diff --git a/README.md b/README.md index 077a8a9..4052ba4 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,13 @@ archRulesAggregate { } ``` +#### Filtering specific rules +If you would like to only display certain rules or rule classes on the CLI, you can use the following flags. +(Note: these rules must still be on the classpath and not excluded.) +```commandline +./gradlew archRulesConsoleReport --rule-name=deprecated --rule-class=com.netflix.nebula.archrules.nullability +``` + ## How it works The Archrules Library plugin produces a separate Jar for the `archRules` sourceset, which is exposed as an alternate variant of the library. It also will automatically generate a `META-INF/services` file which contains a reference for each implementation of `com.netflix.nebula.archrules.core.ArchRulesService` to declare it as a service provider. diff --git a/nebula-archrules-gradle-plugin/src/main/kotlin/com/netflix/nebula/archrules/gradle/PrintConsoleReportTask.kt b/nebula-archrules-gradle-plugin/src/main/kotlin/com/netflix/nebula/archrules/gradle/PrintConsoleReportTask.kt index b96b012..bf121a3 100644 --- a/nebula-archrules-gradle-plugin/src/main/kotlin/com/netflix/nebula/archrules/gradle/PrintConsoleReportTask.kt +++ b/nebula-archrules-gradle-plugin/src/main/kotlin/com/netflix/nebula/archrules/gradle/PrintConsoleReportTask.kt @@ -3,9 +3,9 @@ package com.netflix.nebula.archrules.gradle import com.tngtech.archunit.lang.Priority import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.* +import org.gradle.api.tasks.options.Option import org.gradle.internal.logging.text.StyledTextOutput import org.gradle.internal.logging.text.StyledTextOutputFactory import org.gradle.kotlin.dsl.support.get @@ -38,12 +38,31 @@ abstract class PrintConsoleReportTask : DefaultTask() { @get:Optional abstract val detailsThreshold: Property + private var filteredRules: List = emptyList() + private var filteredRuleClasses: List = emptyList() + + @Option(option = "rule-name", description = "Print only results for the specified rule name(s). Can be specified multiple times.") + fun filterByRuleName(rules: List) { + filteredRules = rules + } + + @Option(option = "rule-class", description = "Print only results for rule classes matching the specified prefix(es). Can be specified multiple times.") + fun filterByRuleClass(ruleClasses: List) { + filteredRuleClasses = ruleClasses + } + @TaskAction fun printReport() { val consoleOutput = services.get().create("archrules") val list = dataFiles.files .filter(File::exists) .flatMap { ViolationsUtil.readDetails(it) } + .filter { + val noFilters = filteredRules.isEmpty() && filteredRuleClasses.isEmpty() + val matchesRule = filteredRules.contains(it.rule().ruleName()) + val matchesClass = filteredRuleClasses.any { prefix -> it.rule().ruleClass().startsWith(prefix) } + noFilters || matchesRule || matchesClass + } .toList() val byRule = ViolationsUtil.consolidatedFailures(list) ViolationsUtil.printSummary(byRule, consoleOutput, summaryForPassingDisabled.get(), logger.isInfoEnabled) diff --git a/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesRunnerPluginTest.kt b/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesRunnerPluginTest.kt index 0864365..5325a42 100644 --- a/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesRunnerPluginTest.kt +++ b/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesRunnerPluginTest.kt @@ -630,6 +630,51 @@ archRules { assertThat(results).isEmpty() } + @Test + fun `ruleName level source set includes`() { + val runner = testProject(projectDir) { + setupConsumerProject { + dependencies("""archRules("com.netflix.nebula:archrules-nullability:0.+")""") + } + } + + val result = runner.run("archRulesConsoleReport", "--rule-name=no Optional class fields", "--rule-name=deprecated", "--stacktrace") + assertThat(result.task(":checkArchRulesMain")).hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE) + + assertThat(result.output) + .contains("deprecated") + .contains("no Optional class fields") + .doesNotContain("deprecatedForRemoval") + } + + @Test + fun `ruleClass level source set includes`() { + val runner = testProject(projectDir) { + setupConsumerProject { + dependencies(""" + archRules("com.netflix.nebula:archrules-nullability:0.+") + archRules("com.netflix.nebula:archrules-joda:0.+") + """) + } + } + + val result = runner.run("archRulesConsoleReport", "--rule-class=com.netflix.nebula.archrules.nullability", "--rule-name=jodaRule", "--stacktrace") + assertThat(result.task(":checkArchRulesMain")).hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE) + + val mainReport = projectDir.resolve("build/reports/archrules/main.data") + val nullabilityRuleNames = readDetails(mainReport) + .filter { it.rule.ruleClass().startsWith("com.netflix.nebula.archrules.nullability") } + .map { it.rule.ruleName() } + .distinct() + + assertThat(nullabilityRuleNames).isNotEmpty() + nullabilityRuleNames.forEach { ruleName -> + assertThat(result.output).contains(ruleName) + } + assertThat(result.output).contains("jodaRule") + assertThat(result.output).doesNotContain("com.netflix.nebula.archrules.deprecation") + } + @Test fun `invalid priority string logs warning and does not override`() { val runner = testProject(projectDir) {