All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.name.remal.gradle_plugins.plugins.code_quality.BaseCodeQualityFindBugsResultsTask.kt Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
package name.remal.gradle_plugins.plugins.code_quality

import com.google.common.reflect.TypeToken
import groovy.lang.Closure
import groovy.lang.Closure.DELEGATE_FIRST
import groovy.lang.DelegatesTo
import name.remal.asClass
import name.remal.forceDeleteRecursively
import name.remal.gradle_plugins.dsl.extensions.configureWith
import name.remal.gradle_plugins.dsl.extensions.doAfter
import name.remal.gradle_plugins.dsl.extensions.getOrNull
import name.remal.gradle_plugins.dsl.extensions.include
import name.remal.gradle_plugins.dsl.extensions.setDefaultDestinationForTask
import name.remal.gradle_plugins.dsl.extensions.sourceDirs
import name.remal.gradle_plugins.dsl.extensions.taskReportContainerClass
import name.remal.gradle_plugins.dsl.extensions.unwrapGradleGenerated
import name.remal.gradle_plugins.dsl.utils.code_quality.FindBugsReport
import name.remal.gradle_plugins.dsl.utils.code_quality.createConsoleMessages
import name.remal.gradle_plugins.dsl.utils.code_quality.readFindBugsReportXml
import name.remal.gradle_plugins.dsl.utils.code_quality.writeHtmlTo
import name.remal.gradle_plugins.dsl.utils.code_quality.writeXmlTo
import name.remal.gradle_plugins.dsl.utils.retrieveClassNameFromJavaSource
import name.remal.nullIf
import name.remal.nullIfEmpty
import name.remal.uncheckedCast
import name.remal.version.Version
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.model.ObjectFactory
import org.gradle.api.reporting.Report.OutputType.DIRECTORY
import org.gradle.api.reporting.ReportContainer
import org.gradle.api.reporting.Reporting
import org.gradle.api.reporting.SingleFileReport
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.VerificationTask
import org.gradle.initialization.BuildCancellationToken
import org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP
import java.io.File
import java.lang.System.currentTimeMillis
import java.lang.reflect.ParameterizedType
import javax.inject.Inject

abstract class BaseCodeQualityFindBugsResultsTask, ExtensionType : BaseCodeQualityExtension> :
    SourceTask(), VerificationTask, Reporting {

    companion object {
        private const val doSearchSourcesInProjectFiles = false
    }


    @get:Input
    protected abstract val findBugsXmlReportName: String

    protected abstract fun doAnalyze(): FindBugsReport


    @get:Input
    @get:Optional
    protected open val htmlReportName: String?
        get() = null

    @get:Internal
    protected open val toolName = this.javaClass.unwrapGradleGenerated().simpleName.substringBefore("Task")


    init {
        group = VERIFICATION_GROUP
    }


    @get:Internal
    protected val reportsClass: Class = run {
        val type = TypeToken.of(this.javaClass).getSupertype(BaseCodeQualityFindBugsResultsTask::class.java).type
        if (type !is ParameterizedType) throw IllegalStateException("$type is not instance of ParameterizedType")
        return@run type.actualTypeArguments[0].asClass().uncheckedCast>()
    }

    @get:Internal
    protected val extensionClass: Class = run {
        val type = TypeToken.of(this.javaClass).getSupertype(BaseCodeQualityFindBugsResultsTask::class.java).type
        if (type !is ParameterizedType) throw IllegalStateException("$type is not instance of ParameterizedType")
        return@run type.actualTypeArguments[1].asClass().uncheckedCast>()
    }

    @get:Internal
    protected val extension: ExtensionType?
        get() = project.getOrNull(extensionClass)

    @get:Input
    @get:Optional
    protected val toolVersion: String?
        get() = extension?.let { it.resolvedToolVersion ?: it.toolVersion }

    @get:Internal
    protected val parsedToolVersion: Version?
        get() = toolVersion?.let(Version::parseOrNull)


    private var ignoreFailures: Boolean = false

    override fun getIgnoreFailures() = ignoreFailures

    override fun setIgnoreFailures(ignoreFailures: Boolean) {
        this.ignoreFailures = ignoreFailures
    }


    private val reports: ReportsType = objectFactory.newInstance(reportsClass.taskReportContainerClass, this).apply {
        getByName(findBugsXmlReportName).isEnabled = true
    }

    private val findBugsXmlReport get() = reports.getByName(findBugsXmlReportName)
    private val htmlReport get() = htmlReportName?.let(reports::getByName)

    @Nested
    override fun getReports() = reports

    override fun reports(@DelegatesTo(strategy = DELEGATE_FIRST) closure: Closure) = reports.configureWith(closure)
    override fun reports(configureAction: Action) = reports.configureWith(configureAction)

    init {
        onlyIf {
            reports.forEach { it.setDefaultDestinationForTask(this) }
            return@onlyIf true
        }
    }


    @TaskAction
    fun analyze() {
        val gradleProject = project
        val findBugsReport = doAnalyze().apply {
            tool = tool.nullIfEmpty() ?: toolName
            version = version.nullIfEmpty()
                ?: extension?.resolvedToolVersion.nullIfEmpty()
                    ?: extension?.toolVersion.nullIfEmpty()
            analysisTimestamp = analysisTimestamp ?: currentTimeMillis()

            project {
                name(gradleProject.displayName)
                sourceDirs.forEach(::srcDir)
            }


            fun getSourceFile(sourceFile: String): File? {
                File(sourceFile).takeIf(File::isAbsolute)?.takeIf(File::isFile)?.let { return it }

                source.include(sourceFile).files.let { files ->
                    if (files.size >= 2) {
                        logger.warn("Source files tree has more than one '{}' files: {}", sourceFile, files.joinToString(", "))
                        return null
                    } else {
                        files.singleOrNull()?.let { return it }
                    }
                }

                if (doSearchSourcesInProjectFiles) {
                    return [email protected](sourceFile).takeIf(File::isFile)?.let { return it }
                } else {
                    return null
                }
            }

            bugs.forEach forEachBug@{ bug ->
                val location = bug.location ?: return@forEachBug
                if (location.className != null) return@forEachBug
                val sourceFilePath = location.sourceFile ?: return@forEachBug

                if (sourceFilePath.endsWith(".java")) {
                    val sourceFile = getSourceFile(sourceFilePath)
                    if (sourceFile != null) {
                        location.className = retrieveClassNameFromJavaSource(
                            sourceFile = sourceFile,
                            line = location.startLine,
                            column = location.startLineOffset
                        )
                    }
                }
            }
        }

        findBugsReport.writeXmlTo(findBugsXmlReport.destination)

        htmlReport.nullIf { !enabled }?.let { report ->
            val destination = report.destination
            destination.forceDeleteRecursively()
            val htmlFile = if (DIRECTORY == report.outputType) destination.resolve("index.html") else destination
            findBugsReport.writeHtmlTo(htmlFile, this)
        }


        didWork = true

        if (!getIgnoreFailures() && findBugsReport.bugs.isNotEmpty()) {
            throw GradleException(
                "%d %s violations were found".format(
                    findBugsReport.bugs.size,
                    toolName
                )
            )
        }
    }

    init {
        doAfter {
            val xmlReportFile = findBugsXmlReport.destination.nullIf { !isFile } ?: return@doAfter
            readFindBugsReportXml(xmlReportFile)
                .createConsoleMessages(this)
                .forEach { logger.error("\n{}", it) }
        }
    }


    @get:Inject
    protected open val objectFactory: ObjectFactory
        get() = TODO()

    @get:Inject
    protected open val buildCancellationToken: BuildCancellationToken
        get() = TODO()

    protected fun handleExtension(handler: (extension: ExtensionType) -> Unit) {
        onlyIf {
            extension?.let(handler)
            return@onlyIf true
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy