Skip to content

Commit

Permalink
feat: can set severity and filter duplicate class warnings.
Browse files Browse the repository at this point in the history
  • Loading branch information
autonomousapps committed Dec 14, 2024
1 parent 67e1b87 commit 94fcafd
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 24 deletions.
4 changes: 4 additions & 0 deletions src/main/kotlin/com/autonomousapps/extension/IssueHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ abstract class IssueHandler @Inject constructor(
return globalDslService.unusedAnnotationProcessorsIssueFor(projectPath)
}

internal fun onDuplicateClassWarnings(projectPath: String): List<Provider<Behavior>> {
return globalDslService.onDuplicateClassWarnings(projectPath)
}

internal fun redundantPluginsIssueFor(projectPath: String): Provider<Behavior> {
return globalDslService.redundantPluginsIssueFor(projectPath)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ import javax.inject.Inject
* ignoreSourceSet(...)
*
* // Specify severity and exclude rules for all types of dependency violations.
* onAny { ... }
* onAny {
* severity(<"fail"|"warn"|"ignore">)
*
* // using version catalog accessors
* exclude(libs.guava, ...)
*
* // using basic string coordinates
* exclude("com.google.guava:guava", ...)
* }
*
* // Specify severity and exclude rules for unused dependencies.
* onUnusedDependencies { ... }
Expand All @@ -47,8 +55,15 @@ import javax.inject.Inject
*
* // Specify severity and exclude rules for module structure advice.
* onModuleStructure {
* severity(<'fail'|'warn'|'ignore'>)
* exclude('android')
* severity(<"fail"|"warn"|"ignore">)
* exclude("android")
* }
*
* onDuplicateClassWarnings {
* severity(<"fail"|"warn"|"ignore">)
*
* // Fully-qualified class reference to exclude, slash- or dot-delimited
* exclude("org/jetbrains/annotations/NotNull", "org.jetbrains.annotations.Nullable")
* }
* }
* }
Expand Down Expand Up @@ -76,6 +91,7 @@ abstract class ProjectIssueHandler @Inject constructor(
internal val runtimeOnlyIssue = objects.newInstance<Issue>()
internal val redundantPluginsIssue = objects.newInstance<Issue>()
internal val moduleStructureIssue = objects.newInstance<Issue>()
internal val duplicateClassWarningsIssue = objects.newInstance<Issue>()

internal val ignoreSourceSets = objects.setProperty<String>()

Expand Down Expand Up @@ -125,4 +141,8 @@ abstract class ProjectIssueHandler @Inject constructor(
fun onModuleStructure(action: Action<Issue>) {
action.execute(moduleStructureIssue)
}

fun onDuplicateClassWarnings(action: Action<Issue>) {
action.execute(duplicateClassWarningsIssue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
package com.autonomousapps.internal.advice

import com.autonomousapps.model.PluginAdvice
import com.autonomousapps.extension.Behavior
import com.autonomousapps.extension.Fail
import com.autonomousapps.internal.DependencyScope
import com.autonomousapps.internal.utils.filterToSet
import com.autonomousapps.internal.utils.lowercase
import com.autonomousapps.model.Advice
import com.autonomousapps.model.DuplicateClass
import com.autonomousapps.model.ModuleAdvice
import com.autonomousapps.model.PluginAdvice
import com.autonomousapps.model.Advice as DependencyAdvice

/** Given the set of all behaviors, determine whether the analysis should fail the build. */
Expand All @@ -20,20 +21,33 @@ internal class SeverityHandler(
private val incorrectConfigurationBehavior: Pair<Behavior, List<Behavior>>,
private val compileOnlyBehavior: Pair<Behavior, List<Behavior>>,
private val unusedProcsBehavior: Pair<Behavior, List<Behavior>>,
private val duplicateClassWarningsBehavior: Pair<Behavior, List<Behavior>>,
private val redundantPluginsBehavior: Behavior,
private val moduleStructureBehavior: Behavior,
) {

fun shouldFailDeps(advice: Set<DependencyAdvice>): Boolean {
return shouldFailFor(anyBehavior, advice) ||
shouldFailFor(unusedDependenciesBehavior, advice.filterToSet { it.isRemove() }) ||
shouldFailFor(usedTransitiveDependenciesBehavior, advice.filterToSet { it.isAdd() }) ||
shouldFailFor(incorrectConfigurationBehavior, advice.filterToSet { it.isChange() }) ||
shouldFailFor(compileOnlyBehavior, advice.filterToSet { it.isCompileOnly() }) ||
shouldFailFor(unusedProcsBehavior, advice.filterToSet { it.isProcessor() })
return shouldFailForAdvice(anyBehavior, advice) ||
shouldFailForAdvice(unusedDependenciesBehavior, advice.filterToSet { it.isRemove() }) ||
shouldFailForAdvice(usedTransitiveDependenciesBehavior, advice.filterToSet { it.isAdd() }) ||
shouldFailForAdvice(incorrectConfigurationBehavior, advice.filterToSet { it.isChange() }) ||
shouldFailForAdvice(compileOnlyBehavior, advice.filterToSet { it.isCompileOnly() }) ||
shouldFailForAdvice(unusedProcsBehavior, advice.filterToSet { it.isProcessor() })
}

fun shouldFailPlugins(pluginAdvice: Set<PluginAdvice>): Boolean {
return (redundantPluginsBehavior.isFail() || anyBehavior.first.isFail()) && pluginAdvice.isNotEmpty()
}

fun shouldFailModuleStructure(moduleAdvice: Set<ModuleAdvice>): Boolean {
return (moduleStructureBehavior.isFail() || anyBehavior.first.isFail()) && ModuleAdvice.isNotEmpty(moduleAdvice)
}

fun shouldFailDuplicateClasses(duplicateClasses: Set<DuplicateClass>): Boolean {
return shouldFailForDuplicateClasses(duplicateClassWarningsBehavior, duplicateClasses)
}

private fun shouldFailFor(
private fun shouldFailForAdvice(
spec: Pair<Behavior, List<Behavior>>,
advice: Set<DependencyAdvice>,
): Boolean {
Expand All @@ -50,7 +64,6 @@ internal class SeverityHandler(
b.sourceSetName == from || b.sourceSetName == to
}


// Looking for a match between sourceSet-specific behavior and advice.
var shouldFail = false
behaviors.forEach { b ->
Expand All @@ -71,12 +84,37 @@ internal class SeverityHandler(
return advice.any(bySourceSets) || (spec.first.isFail() && globalAdvice.isNotEmpty())
}

fun shouldFailPlugins(pluginAdvice: Set<PluginAdvice>): Boolean {
return (redundantPluginsBehavior.isFail() || anyBehavior.first.isFail()) && pluginAdvice.isNotEmpty()
}
private fun shouldFailForDuplicateClasses(
spec: Pair<Behavior, List<Behavior>>,
duplicateClasses: Set<DuplicateClass>,
): Boolean {
// Seed the "global" warnings with the set of all possible warnings. Later on we'll drain this set as elements of it
// are "consumed" by sourceSet-specific behaviors.
val globalAdvice = duplicateClasses.toMutableSet()

fun shouldFailModuleStructure(moduleAdvice: Set<ModuleAdvice>): Boolean {
return (moduleStructureBehavior.isFail() || anyBehavior.first.isFail()) && ModuleAdvice.isNotEmpty(moduleAdvice)
val bySourceSets: (DuplicateClass) -> Boolean = { d ->
// These are the custom behaviors, if any, associated with the source sets represented by this warning.
val behaviors = spec.second.filter { b ->
b.sourceSetName == DependencyScope.sourceSetName(d.classpathName)
}

// Looking for a match between sourceSet-specific behavior and warning.
var shouldFail = false
behaviors.forEach { b ->
val s = b.sourceSetName.lowercase()
val from = d.classpathName.lowercase().startsWith(s)

if (from) {
shouldFail = shouldFail || b.isFail()
globalAdvice.remove(d)
}
}

shouldFail
}

// If all advice is sourceSet-specific, then globalAdvice will be empty.
return duplicateClasses.any(bySourceSets) || (spec.first.isFail() && globalAdvice.isNotEmpty())
}

private fun Behavior.isFail(): Boolean = this is Fail
Expand Down
7 changes: 7 additions & 0 deletions src/main/kotlin/com/autonomousapps/model/DuplicateClass.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.autonomousapps.model

import com.autonomousapps.extension.Behavior
import com.autonomousapps.internal.utils.LexicographicIterableComparator
import com.autonomousapps.model.declaration.Variant
import com.squareup.moshi.JsonClass
Expand All @@ -25,6 +26,12 @@ data class DuplicateClass(
const val RUNTIME_CLASSPATH_NAME = "runtime"
}

private val dotty = className.replace('/', '.')

internal fun containsMatchIn(behavior: Behavior): Boolean {
return behavior.filter.contains(className) || behavior.filter.contains(dotty)
}

override fun compareTo(other: DuplicateClass): Int {
return compareBy(DuplicateClass::variant)
.thenBy(DuplicateClass::classpathName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ abstract class GlobalDslService @Inject constructor(
return issuesFor(projectPath) { it.unusedAnnotationProcessorsIssue }
}

internal fun onDuplicateClassWarnings(projectPath: String): List<Provider<Behavior>> {
return issuesFor(projectPath) { it.duplicateClassWarningsIssue }
}

internal fun redundantPluginsIssueFor(projectPath: String): Provider<Behavior> {
return overlay(all.redundantPluginsIssue, projects.findByName(projectPath)?.redundantPluginsIssue)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,7 @@ internal class ProjectPlugin(private val project: Project) {
compileOnlyBehavior.addAll(compileOnlyIssueFor(theProjectPath))
runtimeOnlyBehavior.addAll(runtimeOnlyIssueFor(theProjectPath))
unusedProcsBehavior.addAll(unusedAnnotationProcessorsIssueFor(theProjectPath))
duplicateClassWarningsBehavior.addAll(onDuplicateClassWarnings(theProjectPath))

// These don't have sourceSet-specific behaviors
redundantPluginsBehavior.set(redundantPluginsIssueFor(theProjectPath))
Expand Down
56 changes: 49 additions & 7 deletions src/main/kotlin/com/autonomousapps/tasks/FilterAdviceTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
package com.autonomousapps.tasks

import com.autonomousapps.model.PluginAdvice
import com.autonomousapps.extension.Behavior
import com.autonomousapps.extension.Ignore
import com.autonomousapps.extension.Issue
import com.autonomousapps.internal.DependencyScope
import com.autonomousapps.internal.advice.SeverityHandler
import com.autonomousapps.internal.utils.*
import com.autonomousapps.model.Advice
import com.autonomousapps.model.ModuleAdvice
import com.autonomousapps.model.ProjectAdvice
import com.autonomousapps.internal.utils.bufferWriteJson
import com.autonomousapps.internal.utils.fromJson
import com.autonomousapps.internal.utils.getAndDelete
import com.autonomousapps.model.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
Expand Down Expand Up @@ -62,6 +61,9 @@ abstract class FilterAdviceTask @Inject constructor(
@get:Input
abstract val runtimeOnlyBehavior: ListProperty<Behavior>

@get:Input
abstract val duplicateClassWarningsBehavior: ListProperty<Behavior>

@get:Input
abstract val redundantPluginsBehavior: Property<Behavior>

Expand All @@ -83,6 +85,7 @@ abstract class FilterAdviceTask @Inject constructor(
unusedProcsBehavior.set(this@FilterAdviceTask.unusedProcsBehavior)
compileOnlyBehavior.set(this@FilterAdviceTask.compileOnlyBehavior)
runtimeOnlyBehavior.set(this@FilterAdviceTask.runtimeOnlyBehavior)
duplicateClassWarningsBehavior.set(this@FilterAdviceTask.duplicateClassWarningsBehavior)
redundantPluginsBehavior.set(this@FilterAdviceTask.redundantPluginsBehavior)
moduleStructureBehavior.set(this@FilterAdviceTask.moduleStructureBehavior)
output.set(this@FilterAdviceTask.output)
Expand All @@ -100,6 +103,7 @@ abstract class FilterAdviceTask @Inject constructor(
val unusedProcsBehavior: ListProperty<Behavior>
val compileOnlyBehavior: ListProperty<Behavior>
val runtimeOnlyBehavior: ListProperty<Behavior>
val duplicateClassWarningsBehavior: ListProperty<Behavior>
val redundantPluginsBehavior: Property<Behavior>
val moduleStructureBehavior: Property<Behavior>
val output: RegularFileProperty
Expand All @@ -117,6 +121,7 @@ abstract class FilterAdviceTask @Inject constructor(
private val unusedProcsBehavior = partition(parameters.unusedProcsBehavior.get())
private val compileOnlyBehavior = partition(parameters.compileOnlyBehavior.get())
private val runtimeOnlyBehavior = partition(parameters.runtimeOnlyBehavior.get())
private val duplicateClassWarningsBehavior = partition(parameters.duplicateClassWarningsBehavior.get())

private val redundantPluginsBehavior = parameters.redundantPluginsBehavior.get()
private val moduleStructureBehavior = parameters.moduleStructureBehavior.get()
Expand Down Expand Up @@ -162,7 +167,11 @@ abstract class FilterAdviceTask @Inject constructor(
.filterNot {
moduleStructureBehavior is Ignore || it.shouldIgnore(moduleStructureBehavior)
}
.toSet()
.toSortedSet()

val duplicateClassWarnings = projectAdvice.warning.duplicateClasses.asSequence()
.filterOf(duplicateClassWarningsBehavior)
.toSortedSet()

val severityHandler = SeverityHandler(
anyBehavior = anyBehavior,
Expand All @@ -171,18 +180,20 @@ abstract class FilterAdviceTask @Inject constructor(
incorrectConfigurationBehavior = incorrectConfigurationBehavior,
unusedProcsBehavior = unusedProcsBehavior,
compileOnlyBehavior = compileOnlyBehavior,
duplicateClassWarningsBehavior = duplicateClassWarningsBehavior,
redundantPluginsBehavior = redundantPluginsBehavior,
moduleStructureBehavior = moduleStructureBehavior,
)
val shouldFailDeps = severityHandler.shouldFailDeps(dependencyAdvice)
val shouldFailPlugins = severityHandler.shouldFailPlugins(pluginAdvice)
val shouldFailModuleStructure = severityHandler.shouldFailModuleStructure(moduleAdvice)
val shouldFailDuplicateClasses = severityHandler.shouldFailDuplicateClasses(duplicateClassWarnings)

val filteredAdvice = projectAdvice.copy(
dependencyAdvice = dependencyAdvice,
pluginAdvice = pluginAdvice,
moduleAdvice = moduleAdvice,
shouldFail = shouldFailDeps || shouldFailPlugins || shouldFailModuleStructure
shouldFail = shouldFailDeps || shouldFailPlugins || shouldFailModuleStructure || shouldFailDuplicateClasses
)

output.bufferWriteJson(filteredAdvice)
Expand Down Expand Up @@ -238,6 +249,37 @@ abstract class FilterAdviceTask @Inject constructor(
}
else this
}

private fun Sequence<DuplicateClass>.filterOf(
behaviorSpec: Pair<Behavior, List<Behavior>>,
): Sequence<DuplicateClass> {
val globalBehavior = behaviorSpec.first
val sourceSetsBehavior = behaviorSpec.second

val byGlobal: (DuplicateClass) -> Boolean = { d ->
globalBehavior is Ignore
|| d.containsMatchIn(globalBehavior)
}

val bySourceSets: (DuplicateClass) -> Boolean = { d ->
// These are the custom behaviors, if any, associated with the source sets represented by this warning.
val behaviors = sourceSetsBehavior.filter { b ->
b.sourceSetName == DependencyScope.sourceSetName(d.classpathName)
}

// reduce() will fail on an empty collection, so use reduceOrNull().
behaviors.map {
it is Ignore
|| d.containsMatchIn(it)
}.reduceOrNull { acc, b ->
acc || b
} ?: false
}

return filterNot { duplicateClass ->
(byGlobal(duplicateClass) || bySourceSets(duplicateClass))
}
}
}

companion object {
Expand Down

0 comments on commit 94fcafd

Please sign in to comment.