Skip to content

Commit

Permalink
Merge pull request #469 from Tapchicoma/456/reset-editorconfig-cache
Browse files Browse the repository at this point in the history
Reset KtLint internal cache on .editorconfig files change
  • Loading branch information
Tapchicoma authored Apr 27, 2021
2 parents e6e5609 + b59f2bc commit 51b2c4b
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 73 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- Plugin fails to apply on non-Kotlin projects ([#443](https://github.com/JLLeitschuh/ktlint-gradle/issues/443))
- Pre-commit hook adds entire file to commit when only part of the file was indexed ([#470](https://github.com/JLLeitschuh/ktlint-gradle/pull/470))
- Pre-commit hook doesn't format files that have been renamed ([#471](https://github.com/JLLeitschuh/ktlint-gradle/pull/471))
- Reset KtLint internal caches on any `.editorconfig` files changes ([#456](https://github.com/JLLeitschuh/ktlint-gradle/issues/456))
### Removed
- ?

Expand Down
55 changes: 21 additions & 34 deletions plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/PluginUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import net.swiftzer.semver.SemVer
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
Expand All @@ -18,6 +17,7 @@ import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.TaskProvider
import org.gradle.language.base.plugins.LifecycleBasePlugin
import java.io.File
import java.nio.file.Files
import java.nio.file.Path

internal inline fun <reified T : Task> Project.registerTask(
Expand All @@ -30,63 +30,50 @@ internal inline fun <reified T : Task> Project.registerTask(
internal const val EDITOR_CONFIG_FILE_NAME = ".editorconfig"

internal fun getEditorConfigFiles(
currentProject: Project,
currentProjectDir: Path,
additionalEditorconfigFile: RegularFileProperty
): FileCollection {
var editorConfigFileCollection = searchEditorConfigFiles(
currentProject,
currentProject.projectDir.toPath(),
currentProject.files()
): Set<Path> {
val result = mutableSetOf<Path>()
searchEditorConfigFiles(
currentProjectDir,
result
)

if (additionalEditorconfigFile.isPresent) {
editorConfigFileCollection = editorConfigFileCollection.plus(
currentProject.files(additionalEditorconfigFile.asFile.get().toPath())
result.add(
additionalEditorconfigFile.asFile.get().toPath()
)
}

return editorConfigFileCollection
return result
}

private tailrec fun searchEditorConfigFiles(
project: Project,
projectPath: Path,
fileCollection: FileCollection
): FileCollection {
result: MutableSet<Path>
) {
val editorConfigFC = projectPath.resolve(EDITOR_CONFIG_FILE_NAME)
val outputCollection = if (editorConfigFC.toFile().exists()) {
fileCollection.plus(project.files(editorConfigFC))
} else {
fileCollection
if (Files.exists(editorConfigFC)) {
result.add(editorConfigFC.toAbsolutePath())
}

val parentDir = projectPath.parent
return if (parentDir != null &&
projectPath != project.rootDir.toPath() &&
if (parentDir != null &&
!editorConfigFC.isRootEditorConfig()
) {
searchEditorConfigFiles(project, parentDir, outputCollection)
} else {
outputCollection
searchEditorConfigFiles(parentDir, result)
}
}

private val editorConfigRootRegex = "^root\\s?=\\s?true\\R".toRegex()

private fun Path.isRootEditorConfig(): Boolean {
val asFile = toFile()
if (!asFile.exists() || !asFile.canRead()) return false

val reader = asFile.bufferedReader()
var fileLine = reader.readLine()
while (fileLine != null) {
if (fileLine.contains(editorConfigRootRegex)) {
return true
}
fileLine = reader.readLine()
}
if (!Files.exists(this) || !Files.isReadable(this)) return false

return false
toFile().useLines { lines ->
val isRoot = lines.firstOrNull { it.contains(editorConfigRootRegex) }
return@isRootEditorConfig isRoot != null
}
}

internal const val VERIFICATION_GROUP = LifecycleBasePlugin.VERIFICATION_GROUP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.gradle.api.GradleException
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.file.FileType
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
Expand All @@ -21,6 +22,9 @@ import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.SourceTask
import org.gradle.work.ChangeType
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
import org.gradle.workers.WorkerExecutor
import org.jlleitschuh.gradle.ktlint.FILTER_INCLUDE_PROPERTY_NAME
import org.jlleitschuh.gradle.ktlint.KOTLIN_EXTENSIONS
Expand All @@ -30,7 +34,6 @@ import org.jlleitschuh.gradle.ktlint.intermediateResultsBuildDir
import org.jlleitschuh.gradle.ktlint.property
import org.jlleitschuh.gradle.ktlint.worker.KtLintWorkAction
import java.io.File
import java.util.concurrent.Callable
import javax.inject.Inject

@Suppress("UnstableApiUsage")
Expand All @@ -46,13 +49,17 @@ abstract class BaseKtLintCheckTask @Inject constructor(
@get:Internal
internal abstract val additionalEditorconfigFile: RegularFileProperty

@Suppress("unused")
@get:Incremental
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:InputFiles
internal val editorConfigFiles: FileCollection by lazy(LazyThreadSafetyMode.NONE) {
// Gradle will lazy evaluate this task input only on task execution
getEditorConfigFiles(project, additionalEditorconfigFile)
}
internal val editorConfigFiles: FileCollection = objectFactory.fileCollection().from(
{
getEditorConfigFiles(
projectLayout.projectDirectory.asFile.toPath(),
additionalEditorconfigFile
)
}
)

@get:Input
internal abstract val ktLintVersion: Property<String>
Expand Down Expand Up @@ -99,9 +106,7 @@ abstract class BaseKtLintCheckTask @Inject constructor(
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:InputFiles
internal val stableSources: FileCollection = project.files(
Callable {
return@Callable source
}
{ source }
)

@get:PathSensitive(PathSensitivity.RELATIVE)
Expand All @@ -116,11 +121,28 @@ abstract class BaseKtLintCheckTask @Inject constructor(
)

protected fun runLint(
filesToCheck: Set<File>,
inputChanges: InputChanges?,
formatSources: Boolean,
) {
checkDisabledRulesSupportedKtLintVersion()

val editorConfigUpdated = wasEditorConfigFilesUpdated(inputChanges)
val filesToCheck = if (formatSources || editorConfigUpdated || inputChanges == null) {
stableSources.files
} else {
getChangedSources(inputChanges)
}

logger.info("Executing ${if (inputChanges?.isIncremental == true) "incrementally" else "non-incrementally"}")
logger.info("Editorconfig files were changed: $editorConfigUpdated")
if (filesToCheck.isEmpty()) {
logger.info("Skipping. No files to lint")
didWork = false
return
} else {
logger.debug("Linting files: ${filesToCheck.joinToString()}")
}

// Process isolation is used here to run KtLint in a separate java process.
// This allows to better isolate work actions from different projects tasks between each other
// and to not pollute Gradle daemon heap, which otherwise greatly increases GC time.
Expand All @@ -141,9 +163,28 @@ abstract class BaseKtLintCheckTask @Inject constructor(
params.formatSource.set(formatSources)
params.discoveredErrorsFile.set(discoveredErrors)
params.ktLintVersion.set(ktLintVersion)
params.editorconfigFilesWereChanged.set(editorConfigUpdated)
}
}

private fun wasEditorConfigFilesUpdated(
inputChanges: InputChanges?
) = inputChanges != null &&
inputChanges.isIncremental &&
!inputChanges.getFileChanges(editorConfigFiles).none()

private fun getChangedSources(
inputChanges: InputChanges
): Set<File> = inputChanges
.getFileChanges(stableSources)
.asSequence()
.filter {
it.fileType != FileType.DIRECTORY &&
it.changeType != ChangeType.REMOVED
}
.map { it.file }
.toSet()

private fun checkDisabledRulesSupportedKtLintVersion() {
if (disabledRules.get().isNotEmpty() &&
SemVer.parse(ktLintVersion.get()) < SemVer(0, 34, 2)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package org.jlleitschuh.gradle.ktlint.tasks

import org.gradle.api.file.FileType
import org.gradle.api.file.ProjectLayout
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.TaskAction
import org.gradle.work.ChangeType
import org.gradle.work.InputChanges
import org.gradle.workers.WorkerExecutor
import javax.inject.Inject
Expand All @@ -24,26 +22,7 @@ abstract class KtLintCheckTask @Inject constructor(

@TaskAction
fun lint(inputChanges: InputChanges) {
logger.info("Executing ${if (inputChanges.isIncremental) "incrementally" else "non-incrementally"}")

val filesToLint = inputChanges
.getFileChanges(stableSources)
.asSequence()
.filter {
it.fileType != FileType.DIRECTORY &&
it.changeType != ChangeType.REMOVED
}
.map { it.file }
.toSet()

if (filesToLint.isEmpty()) {
didWork = false
logger.info("No ${ChangeType.ADDED} or ${ChangeType.MODIFIED} files that need to be linted")
} else {
logger.debug("Files changed: $filesToLint")

runLint(filesToLint, false)
}
runLint(inputChanges, false)
}

internal companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ abstract class KtLintFormatTask @Inject constructor(

@TaskAction
fun format() {
runLint(stableSources.files, true)
runLint(null, true)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.pinterest.ktlint.core.RuleSetProvider
import net.swiftzer.semver.SemVer
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.workers.WorkAction
Expand All @@ -15,6 +16,9 @@ import java.util.ServiceLoader

@Suppress("UnstableApiUsage")
abstract class KtLintWorkAction : WorkAction<KtLintWorkAction.KtLintWorkParameters> {

private val logger = Logging.getLogger("ktlint-worker")

override fun execute() {
val ruleSets = loadRuleSetsAndFilterThem(
parameters.enableExperimental.getOrElse(false),
Expand All @@ -30,6 +34,8 @@ abstract class KtLintWorkAction : WorkAction<KtLintWorkAction.KtLintWorkParamete
val debug = parameters.debug.get()
val formatSource = parameters.formatSource.getOrElse(false)

resetEditorconfigCache()

val result = mutableListOf<LintErrorResult>()

parameters.filesToLint.files.forEach {
Expand Down Expand Up @@ -75,6 +81,14 @@ abstract class KtLintWorkAction : WorkAction<KtLintWorkAction.KtLintWorkParamete
)
}

private fun resetEditorconfigCache() {
if (parameters.editorconfigFilesWereChanged.get()) {
logger.info("Resetting KtLint caches")
// Calling trimMemory() will also reset internal loaded `.editorconfig` cache
KtLint.trimMemory()
}
}

private fun generateUserData(): Map<String, String> {
val userData = mutableMapOf(
"android" to parameters.android.get().toString()
Expand Down Expand Up @@ -116,5 +130,6 @@ abstract class KtLintWorkAction : WorkAction<KtLintWorkAction.KtLintWorkParamete
val formatSource: Property<Boolean>
val discoveredErrorsFile: RegularFileProperty
val ktLintVersion: Property<String>
val editorconfigFilesWereChanged: Property<Boolean>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ abstract class EditorConfigTests : AbstractPluginTest() {
assertThat(task(":$lintTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}

projectRoot.modifyEditorconfigFile(100)
build(CHECK_PARENT_TASK_NAME).apply {
projectRoot.modifyEditorconfigFile(10)
buildAndFail(CHECK_PARENT_TASK_NAME).apply {
assertThat(task(":$lintTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.FAILED)
}
}

Expand All @@ -92,11 +93,12 @@ abstract class EditorConfigTests : AbstractPluginTest() {
}

projectRoot.modifyEditorconfigFile(
maxLineLength = 100,
maxLineLength = 10,
filePath = additionalConfigPath
)
build(CHECK_PARENT_TASK_NAME).apply {
buildAndFail(CHECK_PARENT_TASK_NAME).apply {
assertThat(task(":$lintTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.FAILED)
}
}

Expand Down Expand Up @@ -147,13 +149,14 @@ abstract class EditorConfigTests : AbstractPluginTest() {
assertThat(task(":test:module1:$lintTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}

projectWithModulesLocation.modifyEditorconfigFile(100)
projectWithModulesLocation.modifyEditorconfigFile(10)

gradleRunner
.withArguments(":test:module1:$CHECK_PARENT_TASK_NAME")
.forwardOutput()
.build().apply {
.buildAndFail().apply {
assertThat(task(":test:module1:$lintTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
assertThat(task(":test:module1:$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.FAILED)
}
}

Expand Down

0 comments on commit 51b2c4b

Please sign in to comment.