Compare commits

...

12 Commits

Author SHA1 Message Date
Sergey Vlasenko 3d4ada6bbf Merge branch 'feature/java_17_support' of https://git.svc.touchin.ru/TouchInstinct/BuildScripts into incremental_api_generator
# Conflicts:
#	gradle/plugins/src/main/java/apigen/ApiGeneratorPlugin.kt
#	gradle/scripts/stringGenerator.gradle
2024-04-02 16:58:23 +03:00
Sergey Vlasenko a0c62581de feature incremental api generator, small fix string generator 2024-04-02 16:42:48 +03:00
Sergey Vlasenko 2ebdc7e33f Merge pull request 'feature add outputs for apiGenerator, stringGenerator' (#25) from feature/MB-42563 into feature/java_17_support
Reviewed-on: #25
Reviewed-by: Daniil Bakherov <daniil.bakherov@noreply.localhost>
2024-03-25 17:46:19 +03:00
Sergey Vlasenko ab6a83aee0 feature add outputs for apiGenerator, stringGenerator 2024-03-25 12:55:37 +03:00
Sergey Vlasenko 6df29f2101 Merge pull request 'feature add detektAnalysis task' (#24) from detekt_analysis into feature/java_17_support
Reviewed-on: #24
2024-03-20 16:11:56 +03:00
Sergey Vlasenko dca38422f3 feature add detektAnalysis task 2024-03-19 16:52:12 +03:00
Konstantin Kuzhim 5c46386ebf update BuildScripts to include copy task in AndroidLinter.kt 2023-02-07 14:26:50 +07:00
Kirill Nayduik 3cc63ffe5e Ignore WrongTimberUsageDetector error 2022-05-11 19:10:22 +03:00
Kirill Nayduik ec358728eb Set lintConfig file direction 2022-05-11 17:40:08 +03:00
Kirill Nayduik 78024ea1a6 Add script for setting lintOptions 2022-05-11 17:10:27 +03:00
Kirill Nayduik 5e7a39d14d Add comments for temporary workaround to avoid Android Linter configuration error 2022-04-29 22:13:42 +03:00
Kirill Nayduik e175e66e57 Update parcelize implementation 2022-04-28 16:56:21 +03:00
17 changed files with 543 additions and 61 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

@ -6,6 +6,7 @@ import org.gradle.api.Project
import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.findByType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import static_analysis.utils.getApiGeneratorExtension
class ApiGeneratorAndroidPlugin : ApiGeneratorPlugin() { class ApiGeneratorAndroidPlugin : ApiGeneratorPlugin() {
@ -13,11 +14,10 @@ class ApiGeneratorAndroidPlugin : ApiGeneratorPlugin() {
super.apply(target) super.apply(target)
with(target) { with(target) {
val extension = getExtension() val extension = getApiGeneratorExtension()
val outputDir = getDirectoryForGeneration() val outputDir = getDirectoryForGeneration()
extension.outputDirPath = outputDir.path extension.outputDirPath = outputDir.path
extension.recreateOutputDir = true
afterEvaluate { afterEvaluate {
extensions.findByType<LibraryExtension>()?.apply { extensions.findByType<LibraryExtension>()?.apply {

View File

@ -1,13 +1,14 @@
package apigen package apigen
import org.gradle.api.Project import org.gradle.api.Project
import static_analysis.utils.getApiGeneratorExtension
class ApiGeneratorBackendPlugin : ApiGeneratorPlugin() { class ApiGeneratorBackendPlugin : ApiGeneratorPlugin() {
override fun apply(target: Project) { override fun apply(target: Project) {
super.apply(target) super.apply(target)
val extension = target.getExtension() val extension = target.getApiGeneratorExtension()
extension.outputDirPath = target.file("src/main/kotlin").path extension.outputDirPath = target.file("src/main/kotlin").path
extension.recreateOutputDir = false extension.recreateOutputDir = false

View File

@ -5,6 +5,7 @@ import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.repositories import org.gradle.kotlin.dsl.repositories
abstract class ApiGeneratorPlugin : Plugin<Project> { abstract class ApiGeneratorPlugin : Plugin<Project> {
@ -13,6 +14,8 @@ abstract class ApiGeneratorPlugin : Plugin<Project> {
const val API_GENERATOR_CONFIG = "apiGenerator" const val API_GENERATOR_CONFIG = "apiGenerator"
const val API_GENERATOR_EXT_NAME = "apiGenerator" const val API_GENERATOR_EXT_NAME = "apiGenerator"
const val API_GENERATOR_DEFAULT_VERSION = "1.4.0-beta10" const val API_GENERATOR_DEFAULT_VERSION = "1.4.0-beta10"
const val API_GENERATOR_CLEAN_TEMP_DIR_TASK = "apiGeneratorCleanTempDir"
} }
override fun apply(target: Project) { override fun apply(target: Project) {
@ -34,7 +37,8 @@ abstract class ApiGeneratorPlugin : Plugin<Project> {
extensions.create<ApiGeneratorExtension>(API_GENERATOR_EXT_NAME) extensions.create<ApiGeneratorExtension>(API_GENERATOR_EXT_NAME)
val apiGenTask = createApiGeneratorTask() val cleanTask = tasks.create(API_GENERATOR_CLEAN_TEMP_DIR_TASK)
val apiGenTask = getApiGenTask(cleanTask)
gradle.projectsEvaluated { gradle.projectsEvaluated {
tasks.getByName("preBuild").dependsOn(apiGenTask) tasks.getByName("preBuild").dependsOn(apiGenTask)
@ -42,35 +46,15 @@ abstract class ApiGeneratorPlugin : Plugin<Project> {
} }
} }
protected fun Project.getExtension(): ApiGeneratorExtension = extensions.getByName(API_GENERATOR_EXT_NAME) as ApiGeneratorExtension private fun Project.getApiGenTask(cleanTask: Task) = tasks.register(API_GENERATOR_CONFIG, ApiGeneratorTask::class) {
val apiGeneratorTempDir = temporaryDir
tempApiDirDirectory.set(apiGeneratorTempDir.absolutePath)
private fun Project.createApiGeneratorTask(): Task = tasks.create(API_GENERATOR_CONFIG).doLast { // Нужно для удаления временной директории даже в случае краша во время выполнения таски.
finalizedBy(
val extension = getExtension() cleanTask.doFirst {
apiGeneratorTempDir.deleteRecursively()
val pathToApiSchemes = extension.pathToApiSchemes ?: throw IllegalStateException("Configure path to api schemes for api generator plugin") }
val outputLanguage = extension.outputLanguage ?: throw IllegalStateException("Configure output language code for api generator plugin") )
javaexec {
main = "-jar"
workingDir = rootDir
args = listOfNotNull(
configurations.getByName("apiGenerator").asPath,
"generate-client-code",
"--output-language",
outputLanguage.argName,
"--specification-path",
pathToApiSchemes,
"--kotlin-methods-generation-mode".takeIf { outputLanguage.methodOutputType != null },
outputLanguage.methodOutputType?.argName,
"--output-path",
extension.outputDirPath,
"--package-name",
extension.outputPackageName,
"--recreate_output_dirs",
extension.recreateOutputDir.toString()
)
}
} }
} }

View File

@ -0,0 +1,156 @@
package apigen
import apigen.depencency_resolver.ApiModelsDependencyResolver
import apigen.depencency_resolver.CopyApiModelsToTempDirTask
import apigen.depencency_resolver.FileParser
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileTree
import org.gradle.api.file.FileType
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.work.ChangeType
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
import org.gradle.workers.WorkerExecutor
import static_analysis.utils.getApiGeneratorExtension
import static_analysis.utils.toPath
import java.io.File
import java.nio.file.Path
import javax.inject.Inject
abstract class ApiGeneratorTask: DefaultTask() {
@Internal
val extension: ApiGeneratorExtension = project.getApiGeneratorExtension()
@Internal
val pathToApiScheme: String = extension.pathToApiSchemes
?: throw IllegalStateException("Configure path to api schemes for api generator plugin")
@Internal
val outputLanguage: OutputLanguage = extension.outputLanguage
?: throw IllegalStateException("Configure output language code for api generator plugin")
@Inject
abstract fun getWorkerExecutor(): WorkerExecutor
@Incremental
@InputFiles
val inputFiles: FileTree = project.files(pathToApiScheme).asFileTree
@OutputFiles
val outputFiles: FileTree = project.files(extension.outputDirPath).asFileTree
@get:Input
abstract val tempApiDirDirectory: Property<String>
@TaskAction
fun execute(inputChanges: InputChanges): Unit = with(project) {
if (inputChanges.isIncremental) {
handleInputChanges(inputChanges)
} else {
generateApi(
pathToApi = pathToApiScheme,
outputLanguage = outputLanguage,
recreateOutputDir = true
)
}
}
private fun generateApi(
pathToApi: String,
outputLanguage: OutputLanguage,
recreateOutputDir: Boolean
) = with(project) {
javaexec {
main = "-jar"
workingDir = rootDir
args = listOfNotNull(
configurations.getByName("apiGenerator").asPath,
"generate-client-code",
"--output-language",
outputLanguage.argName,
"--specification-path",
pathToApi,
"--kotlin-methods-generation-mode".takeIf { outputLanguage.methodOutputType != null },
outputLanguage.methodOutputType?.argName,
"--output-path",
extension.outputDirPath,
"--package-name",
extension.outputPackageName,
"--recreate_output_dirs",
recreateOutputDir.toString()
)
}
}
private fun handleInputChanges(inputChanges: InputChanges) {
val modifiedFiles = hashSetOf<File>()
inputChanges.getFileChanges(inputFiles)
.filter { it.fileType == FileType.FILE }
.forEach { change ->
when (change.changeType) {
ChangeType.REMOVED -> {
outputFiles.find { it.nameWithoutExtension == change.file.nameWithoutExtension }?.delete()
}
else -> modifiedFiles.add(change.file)
}
}
if (modifiedFiles.isEmpty()) return
handleModifiedFiles(modifiedFiles)
}
private fun handleModifiedFiles(modifiedFiles: MutableCollection<File>) {
val dependencyResolver = ApiModelsDependencyResolver(
pathToApi = pathToApiScheme,
fileParser = FileParser()
)
modifiedFiles.addAll(
dependencyResolver.getDependenciesFromFiles(modifiedFiles)
)
val tempApiDirectory = tempApiDirDirectory.getOrElse("")
if (tempApiDirectory.isBlank()) {
throw IllegalStateException("TempApiDirDirectory is blank or not specified.")
}
copyAllFilesToTempDir(
tempDirPath = tempApiDirectory,
files = modifiedFiles
)
generateApi(
pathToApi = tempApiDirectory,
outputLanguage = outputLanguage,
recreateOutputDir = false
)
}
private fun copyAllFilesToTempDir(tempDirPath: String, files: Collection<File>) {
val workQueue = getWorkerExecutor().noIsolation()
files.forEach { file ->
val newFilePath = pathToApiScheme.toPath()
.relativize(file.absolutePath.toPath()).toString()
val newFileFullPath = Path.of(tempDirPath, newFilePath).toString()
workQueue.submit(CopyApiModelsToTempDirTask::class.java) {
getSourceFile().set(file)
getTargetFilePath().set(newFileFullPath)
}
}
}
}

View File

@ -0,0 +1,59 @@
package apigen.depencency_resolver
import apigen.depencency_resolver.FileParser.Companion.NAME_JSON_KEY
import groovy.json.JsonSlurper
import java.io.File
/**
* Класс для разрешения зависимостей АПИ моделей от других АПИ моделей.
*
* @param pathToApi путь к директории АПИ, где будут искаться зависимости
* @param fileParser класс, ответственный за поиск типов в АПИ модельке.
*/
@Suppress("UNCHECKED_CAST")
class ApiModelsDependencyResolver(
pathToApi: String,
private val fileParser: FileParser
) {
// Мапа для хранения и быстрого поиска типа модельки по файлам, т.к. не всегда имя файла совпадает с типом модельки.
private val modelTypes: HashMap<String, File> = hashMapOf()
private val handledFiles = hashSetOf<File>()
init {
File(pathToApi)
.walk()
.filter { it.isFile }
.forEach { file ->
val json = JsonSlurper().parse(file) as? Map<String, Any?>
val type = json?.getOrDefault(NAME_JSON_KEY, file.nameWithoutExtension).toString()
modelTypes[type] = file
}
}
fun getDependenciesFromFiles(files: Collection<File>): Set<File> =
files.fold(setOf()) { dependencies, file ->
dependencies + getFileDependencies(file)
}
private fun getFileDependencies(file: File): Set<File> {
if (handledFiles.contains(file)) {
return emptySet()
}
handledFiles.add(file)
val json = JsonSlurper().parse(file) as Map<String, Any?>
val files = fileParser.getAllTypeNamesFromJson(json)
.map { type ->
modelTypes[type]
?: throw IllegalArgumentException("Couldn't resolve $type in ${file.name}")
}
.toSet()
return files.fold(files) { dependencies, myFile ->
dependencies + getFileDependencies(myFile)
}
}
}

View File

@ -0,0 +1,22 @@
package apigen.depencency_resolver
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import java.io.File
abstract class CopyApiModelsToTempDirTask: WorkAction<CopyApiModelsToTempDirTaskParams> {
override fun execute() {
val sourceFile = parameters.getSourceFile().asFile.get()
val target = File(parameters.getTargetFilePath().get())
sourceFile.copyTo(target = target)
}
}
interface CopyApiModelsToTempDirTaskParams: WorkParameters {
fun getSourceFile(): RegularFileProperty
fun getTargetFilePath(): Property<String>
}

View File

@ -0,0 +1,103 @@
package apigen.depencency_resolver
@Suppress("UNCHECKED_CAST")
class FileParser {
companion object {
const val NAME_JSON_KEY = "name"
const val PARENT_JSON_KEY = "parent"
const val TYPE_JSON_KEY = "type"
}
private val defaultTypes = hashSetOf(
"Bool",
"String",
"DateTime",
"Long",
"Int",
"Double",
"Decimal",
"Date",
"DateTimeTimestamp",
"Color",
"StringDecimal",
"Url",
"Map",
"null" // Костыль, чтобы не писать проверки на null при получении типа из мапы
)
fun getAllTypeNamesFromJson(model: Map<String, Any?>): Set<String> {
val currentModelType = Type.getType(model[NAME_JSON_KEY].toString())
var generic = ""
if (currentModelType is TypeWithGeneric) {
generic = currentModelType.getGenericTypeName()
}
val parentRawTypeName = model[PARENT_JSON_KEY].toString()
return model.entries
.fold(setOf(parentRawTypeName)) { foundRawTypeNames: Set<String>, jsonEntry: Map.Entry<String, Any?> ->
foldDependencies(foundRawTypeNames, jsonEntry)
}
.minus(defaultTypes)
.minus(generic)
.fold(setOf()) { foundTypeNames, rawTypeName -> foundTypeNames + getTypeFromRawTypeNames(rawTypeName) }
}
private fun foldDependencies(foundTypes: Set<String>, jsonEntry: Map.Entry<String, Any?>): Set<String> {
val value = jsonEntry.value
val newTypes: Set<String> = when {
(value as? Collection<Map<String, Any?>>) != null -> {
getTypeFromCollection(value)
}
(value as? Map<String, Any?>) != null && jsonEntry.key == TYPE_JSON_KEY -> {
val typeName = value[NAME_JSON_KEY].toString()
val parentTypeName = value[PARENT_JSON_KEY].toString()
setOf(typeName, parentTypeName)
}
else -> setOf()
}
return if (newTypes.isEmpty()) {
foundTypes
} else {
foundTypes + newTypes
}
}
private fun getTypeFromCollection(value: Collection<Map<String, Any?>>): Set<String> =
value.fold(setOf()) { types: Set<String>, collectionEntry: Map<String, Any?> ->
types + getAllTypeNamesFromJson(collectionEntry)
}
private fun getTypeFromRawTypeNames(typeName: String): Set<String> {
val type = Type.getType(typeName)
val newTypeNames = mutableSetOf<String>()
when (type) {
is SimpleType -> {
newTypeNames.add(type.getMainTypeName())
}
is ArrayType -> {
newTypeNames.add(type.getMainTypeName())
}
is MapType -> {
newTypeNames.addAll(type.getSubTypeNames())
}
is TypeWithGeneric -> {
newTypeNames.add(type.getMainTypeName())
}
}
return newTypeNames
}
}

View File

@ -0,0 +1,57 @@
package apigen.depencency_resolver
sealed class Type(protected val typeName: String) {
companion object {
private val simpleTypeRegex = Regex("\\w*")
private val mapRegex = Regex("Map<.*?,.*?>")
private val arrayRegex = Regex(".*?\\[?]")
private val typeWithGenericRegex = Regex(".*?<.*?>")
fun getType(typeName: String): Type = when {
typeName.matches(mapRegex) -> MapType(typeName)
typeName.matches(arrayRegex) -> ArrayType(typeName)
typeName.matches(typeWithGenericRegex) -> TypeWithGeneric(typeName)
typeName.matches(simpleTypeRegex) -> SimpleType(typeName)
else -> throw IllegalArgumentException("Cannot define type for $typeName")
}
}
abstract fun getMainTypeName(): String
}
// CardV2
class SimpleType(typeName: String) : Type(typeName) {
override fun getMainTypeName(): String = typeName
}
// String[]
class ArrayType(typeName: String): Type(typeName) {
override fun getMainTypeName(): String = typeName.dropLast(2)
}
// BaseResponse<T>
class TypeWithGeneric(typeName: String): Type(typeName) {
override fun getMainTypeName(): String = typeName
.substringBefore("<")
fun getGenericTypeName(): String = typeName
.substringAfter("<")
.dropLast(1)
}
// Subscription<String, Int>
class MapType(typeName: String): Type(typeName) {
override fun getMainTypeName(): String = typeName.substringBefore("<")
fun getSubTypeNames(): Set<String> = typeName
.substringAfter("<")
.dropLast(1)
.split(",")
.map { it.trim() }
.toSet()
}

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

@ -1,5 +1,7 @@
package static_analysis.utils package static_analysis.utils
import apigen.ApiGeneratorExtension
import apigen.ApiGeneratorPlugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.file.FileTree import org.gradle.api.file.FileTree
import java.io.File import java.io.File
@ -25,3 +27,8 @@ fun Project.getSources(excludes: String): FileTree = files(
.filter { it.exists() && it.isDirectory } .filter { it.exists() && it.isDirectory }
.map { it.path } .map { it.path }
).asFileTree ).asFileTree
fun Project.getApiGeneratorExtension(): ApiGeneratorExtension =
extensions.getByName(ApiGeneratorPlugin.API_GENERATOR_EXT_NAME) as ApiGeneratorExtension
fun Project.getProjectName() = rootDir.absolutePath.substringAfterLast(File.separator)

View File

@ -0,0 +1,28 @@
package static_analysis.utils
import java.io.File
import java.io.IOException
import java.nio.file.Path
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
}
}
fun String.toPath(): Path = Path.of(this)

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

@ -1,9 +1,15 @@
import groovy.json.JsonSlurper import groovy.json.JsonSlurper
import groovy.xml.MarkupBuilder import groovy.xml.MarkupBuilder
task stringGenerator { tasks.register('stringGenerator') {
generate(android.languageMap, project) def sources = android.languageMap
println("Strings generated!")
outputs.files(sources)
doLast {
generate(sources, project)
println("Strings generated!")
}
} }
private def generate(Map<String, String> sources, Project project) { private def generate(Map<String, String> sources, Project project) {

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" />