Skip to content

Commit

Permalink
Gradle plugin: use PatternSet instead of FileCollection for inputs (
Browse files Browse the repository at this point in the history
#1118)

### What's done:
* Changed logic
* Changed examples
* Changed tests
* Added proper annotations for some properties inside gradle plugin (resulting in 'automatic' `NO_SOURCE` statuses wen provided patterns don't match anything)
* Fix deprecation warnings in `diktat-gradle-plugin/build.gradle.kts`

This PR proposes a more convenient API for configuring inputs for gradle task. See #1026 , #1094
  • Loading branch information
petertrr committed Nov 17, 2021
1 parent 0451db5 commit 91708c9
Show file tree
Hide file tree
Showing 16 changed files with 189 additions and 121 deletions.
9 changes: 8 additions & 1 deletion .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ jobs:
path: diktat-ruleset/target/diktat-*.jar
# no need to store artifact longer, it is used only by dependant jobs
retention-days: 1
- name: Upload gradle reports
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: gradle-test-report-${{ runner.os }}
path: 'diktat-gradle-plugin/build/reports/'
retention-days: 1

run_diktat_from_CLI:
name: Run diktat via CLI
Expand Down Expand Up @@ -110,7 +117,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
os: [ windows-latest, macos-latest ]
steps:
- uses: actions/checkout@v2.4.0
- name: Set up JDK 11
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
![deteKT static analysis](https://github.com/cqfn/diKTat/workflows/Run%20deteKT/badge.svg)
![diKTat code style](https://github.com/cqfn/diKTat/workflows/Run%20diKTat%20from%20release%20version/badge.svg?branch=master)
[![License](https://img.shields.io/github/license/cqfn/diKtat)](https://github.com/cqfn/diKTat/blob/master/LICENSE)
[![codecov](https://codecov.io/gh/cqfn/diKTat/branch/master/graph/badge.svg)](https://codecov.io/gh/cqfn/diKTat)
[![codecov](https://codecov.io/gh/diktat-static-analysis/diKTat/branch/master/graph/badge.svg)](https://codecov.io/gh/diktat-static-analysis/diKTat)

[![Releases](https://img.shields.io/github/v/release/cqfn/diKTat)](https://github.com/cqfn/diKTat/releases)
[![Maven Central](https://img.shields.io/maven-central/v/org.cqfn.diktat/diktat-rules)](https://mvnrepository.com/artifact/org.cqfn.diktat)
Expand Down Expand Up @@ -133,9 +133,11 @@ apply(plugin = "org.cqfn.diktat.diktat-gradle-plugin")
You can then configure diktat using `diktat` extension:
```kotlin
diktat {
inputs = files("src/**/*.kt") // file collection that will be checked by diktat
inputs {
include("src/**/*.kt") // path matching this pattern (per PatternFilterable) that will be checked by diktat
exclude("src/test/kotlin/excluded/**") // path matching this pattern will not be checked by diktat
}
debug = true // turn on debug logging
excludes = files("src/test/kotlin/excluded") // these files will not be checked by diktat
}
```

Expand Down
21 changes: 9 additions & 12 deletions diktat-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ repositories {
// default value is needed for correct gradle loading in IDEA; actual value from maven is used during build
val ktlintVersion = project.properties.getOrDefault("ktlintVersion", "0.43.0") as String
val diktatVersion = project.version.takeIf { it.toString() != Project.DEFAULT_VERSION } ?: "0.5.2"
val junitVersion = project.properties.getOrDefault("junitVersion", "5.7.0") as String
val junitVersion = project.properties.getOrDefault("junitVersion", "5.8.1") as String
val jacocoVersion = project.properties.getOrDefault("jacocoVersion", "0.8.7") as String
dependencies {
implementation(kotlin("gradle-plugin-api"))
Expand All @@ -40,6 +40,7 @@ dependencies {
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7")
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
exclude("org.slf4j", "slf4j-log4j12")
}

testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
Expand All @@ -49,6 +50,8 @@ dependencies {
val generateVersionsFile by tasks.registering {
val versionsFile = File("$buildDir/generated/src/generated/Versions.kt")

inputs.property("diktat version", diktatVersion)
inputs.property("ktlint version", ktlintVersion)
outputs.file(versionsFile)

doFirst {
Expand All @@ -72,7 +75,6 @@ tasks.withType<KotlinCompile> {
languageVersion = "1.3"
apiVersion = "1.3"
jvmTarget = "1.8"
useIR = false // for compatibility with older gradle
}

dependsOn.add(generateVersionsFile)
Expand All @@ -93,7 +95,6 @@ java {

// === testing & code coverage, jacoco is run independent from maven
val functionalTestTask by tasks.register<Test>("functionalTest")
val jacocoMergeTask by tasks.register<JacocoMerge>("jacocoMerge")
tasks.withType<Test> {
useJUnitPlatform()
}
Expand All @@ -102,7 +103,7 @@ jacoco.toolVersion = jacocoVersion
// === integration testing
// fixme: should probably use KotlinSourceSet instead
val functionalTest = sourceSets.create("functionalTest") {
compileClasspath += sourceSets.main.get().output + configurations.testRuntimeClasspath
compileClasspath += sourceSets.main.get().output + configurations.testRuntimeClasspath.get()
runtimeClasspath += output + compileClasspath
}
tasks.getByName<Test>("functionalTest") {
Expand All @@ -123,25 +124,21 @@ tasks.getByName<Test>("functionalTest") {
Thread.sleep(5_000)
}
}
finalizedBy(jacocoMergeTask)
finalizedBy(tasks.jacocoTestReport)
}
tasks.check { dependsOn(tasks.jacocoTestReport) }
jacocoTestKit {
applyTo("functionalTestRuntimeOnly", tasks.named("functionalTest"))
}
tasks.getByName("jacocoMerge", JacocoMerge::class) {
dependsOn(functionalTestTask)
tasks.jacocoTestReport {
dependsOn(tasks.withType<Test>())
executionData(
fileTree("$buildDir/jacoco").apply {
include("*.exec")
}
)
}
tasks.jacocoTestReport {
dependsOn(jacocoMergeTask)
executionData("$buildDir/jacoco/jacocoMerge.exec")
reports {
// xml report is used by codecov
xml.isEnabled = true
xml.required.set(true)
}
}
2 changes: 1 addition & 1 deletion diktat-gradle-plugin/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.DisabledOnOs
import org.junit.jupiter.api.condition.OS
import java.io.File

class DiktatGradlePluginFunctionalTest {
Expand Down Expand Up @@ -46,7 +44,7 @@ class DiktatGradlePluginFunctionalTest {
buildFile.appendText(
"""${System.lineSeparator()}
diktat {
inputs = files("src/**/*.kt")
inputs { include("src/**/*.kt") }
reporterType = "json"
output = "test.txt"
}
Expand All @@ -69,7 +67,7 @@ class DiktatGradlePluginFunctionalTest {
buildFile.appendText(
"""${System.lineSeparator()}
diktat {
inputs = files("src/**/*.kt")
inputs { include("src/**/*.kt") }
}
""".trimIndent()
)
Expand All @@ -88,61 +86,37 @@ class DiktatGradlePluginFunctionalTest {
buildFile.appendText(
"""${System.lineSeparator()}
diktat {
inputs = files("src/**/*.kt")
excludes = files("src/**/Test.kt")
inputs {
include("src/**/*.kt")
exclude("src/**/Test.kt")
}
}
""".trimIndent()
)
val result = runDiktat(testProjectDir)
val result = runDiktat(testProjectDir, shouldSucceed = false)

val diktatCheckBuildResult = result.task(":$DIKTAT_CHECK_TASK")
requireNotNull(diktatCheckBuildResult)
Assertions.assertEquals(TaskOutcome.SUCCESS, diktatCheckBuildResult.outcome)
Assertions.assertEquals(TaskOutcome.FAILED, diktatCheckBuildResult.outcome)
}

@Test
fun `should not run diktat with ktlint's default includes when no files match include patterns`() {
buildFile.appendText(
"""${System.lineSeparator()}
diktat {
inputs = files("nonexistent-directory/src/**/*.kt")
inputs { include ("nonexistent-directory/src/**/*.kt") }
}
""".trimIndent()
)
val result = runDiktat(testProjectDir, arguments = listOf("--info"))

// if patterns in gradle are not checked for matching, they are passed to ktlint, which does nothing
val diktatCheckBuildResult = result.task(":$DIKTAT_CHECK_TASK")
requireNotNull(diktatCheckBuildResult)
Assertions.assertEquals(TaskOutcome.SUCCESS, diktatCheckBuildResult.outcome)
Assertions.assertEquals(TaskOutcome.NO_SOURCE, diktatCheckBuildResult.outcome)
Assertions.assertFalse(
result.output.contains("Skipping diktat execution")
)
Assertions.assertFalse(
result.output.contains("Inputs for $DIKTAT_CHECK_TASK do not exist, will not run diktat")
)
}

@Test
fun `should not run diktat with ktlint's default includes when no files match include patterns - 2`() {
buildFile.appendText(
"""${System.lineSeparator()}
diktat {
inputs = fileTree("nonexistent-directory/src").apply { include("**/*.kt") }
}
""".trimIndent()
)
val result = runDiktat(testProjectDir, arguments = listOf("--info"))

val diktatCheckBuildResult = result.task(":$DIKTAT_CHECK_TASK")
requireNotNull(diktatCheckBuildResult)
Assertions.assertEquals(TaskOutcome.SUCCESS, diktatCheckBuildResult.outcome)
Assertions.assertTrue(
result.output.contains("Skipping diktat execution")
)
Assertions.assertTrue(
result.output.contains("Inputs for $DIKTAT_CHECK_TASK do not exist, will not run diktat")
)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class DiktatGradlePluginMultiprojectFunctionalTest {

val diktatCheckRootResult = result.task(":$DIKTAT_CHECK_TASK")
requireNotNull(diktatCheckRootResult)
Assertions.assertEquals(TaskOutcome.SUCCESS, diktatCheckRootResult.outcome) {
Assertions.assertEquals(TaskOutcome.NO_SOURCE, diktatCheckRootResult.outcome) {
"Task for root project with empty sources should succeed"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package org.cqfn.diktat.plugin.gradle

import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.util.PatternFilterable
import org.gradle.api.tasks.util.PatternSet
import java.io.File

/**
* An extension to configure diktat in build.gradle(.kts) file
*/
open class DiktatExtension {
open class DiktatExtension(
private val patternSet: PatternSet
) {
/**
* Boolean flag to support `ignoreFailures` property of [VerificationTask].
*/
Expand All @@ -29,18 +36,31 @@ open class DiktatExtension {
var output: String = ""

/**
* Path to diktat yml config file. Can be either absolute or relative to project's root directory.
* Default value: `diktat-analysis.yml` in rootDir.
* Paths that will be excluded from diktat run
*/
lateinit var diktatConfigFile: File
@Deprecated("Configuration via inputs/excludes is unsupported, use inputs(Action)")
var excludes: FileCollection? = null

/**
* Paths that will be excluded from diktat run
* Paths that will be scanned for .kt(s) files
*/
lateinit var excludes: FileCollection
@Deprecated("Configuration via inputs/excludes is unsupported, use inputs(Action)")
var inputs: FileCollection? = null

/**
* Paths that will be scanned for .kt(s) files
* Path to diktat yml config file. Can be either absolute or relative to project's root directory.
* Default value: `diktat-analysis.yml` in rootDir.
*/
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
lateinit var diktatConfigFile: File

/**
* Configure input files for diktat task
*
* @param action configuration lambda for `PatternFilterable`
*/
lateinit var inputs: FileCollection
fun inputs(action: PatternFilterable.() -> Unit) {
action.invoke(patternSet)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.attributes.Bundling
import org.gradle.api.tasks.util.PatternSet

/**
* Plugin that configures diktat and registers tasks to run diktat
Expand All @@ -16,12 +17,13 @@ class DiktatGradlePlugin : Plugin<Project> {
* @param project a gradle [Project] that the plugin is applied to
*/
override fun apply(project: Project) {
val diktatExtension = project.extensions.create(DIKTAT_EXTENSION, DiktatExtension::class.java).apply {
inputs = project.fileTree("src").apply {
include("**/*.kt")
}
val patternSet = PatternSet()
val diktatExtension = project.extensions.create(
DIKTAT_EXTENSION,
DiktatExtension::class.java,
patternSet
).apply {
diktatConfigFile = project.rootProject.file("diktat-analysis.yml")
excludes = project.files()
}

// only gradle 7+ (or maybe 6.8) will embed kotlin 1.4+, kx.serialization is incompatible with kotlin 1.3, so until then we have to use JavaExec wrapper
Expand All @@ -43,8 +45,8 @@ class DiktatGradlePlugin : Plugin<Project> {
configuration.dependencies.add(project.dependencies.create("org.cqfn.diktat:diktat-rules:$DIKTAT_VERSION"))
}

project.registerDiktatCheckTask(diktatExtension, diktatConfiguration)
project.registerDiktatFixTask(diktatExtension, diktatConfiguration)
project.registerDiktatCheckTask(diktatExtension, diktatConfiguration, patternSet)
project.registerDiktatFixTask(diktatExtension, diktatConfiguration, patternSet)
}

companion object {
Expand Down
Loading

0 comments on commit 91708c9

Please sign in to comment.