Skip to content

Commit 38741bc

Browse files
committed
add console report
add console report task which will run by default add extension which can allow console report to be disabled
1 parent 69fac8b commit 38741bc

File tree

8 files changed

+210
-8
lines changed

8 files changed

+210
-8
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ dependencies {
134134
}
135135
```
136136

137+
The plugin can generate JSON and console reports. Both are enabled by default. The console report can be disabled:
138+
```kotlin
139+
archRules {
140+
consoleReportEnabled = false
141+
}
142+
```
143+
137144
## How it works
138145

139146
The Archrules Library plugin produces a separate Jar for the `archRules` sourceset, which is exposed as an alternate variant of the library.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.netflix.nebula.archrules.gradle;
2+
3+
import com.tngtech.archunit.lang.Priority;
4+
import org.gradle.api.DefaultTask;
5+
import org.gradle.api.provider.ListProperty;
6+
import org.gradle.api.tasks.*;
7+
import org.gradle.internal.logging.text.StyledTextOutput;
8+
import org.gradle.internal.logging.text.StyledTextOutputFactory;
9+
import org.jspecify.annotations.NonNull;
10+
11+
import java.io.File;
12+
import java.util.List;
13+
14+
/**
15+
* Prints summary and detail information about {@link RuleResult}s to the console
16+
*/
17+
@UntrackedTask(because = "Provides console feedback to the user")
18+
abstract public class PrintConsoleReportTask extends DefaultTask {
19+
20+
/**
21+
* The data files to read in. These files should container binary data representing {@link RuleResult}s
22+
* @return all data files to process
23+
*/
24+
@InputFiles
25+
@PathSensitive(PathSensitivity.RELATIVE)
26+
abstract public ListProperty<@NonNull File> getDataFiles();
27+
28+
@TaskAction
29+
public void printReport() {
30+
final var consoleOutput = getServices().get(StyledTextOutputFactory.class).create("archrules");
31+
consoleOutput.style(StyledTextOutput.Style.Header).println("ArchRule summary:").println();
32+
List<RuleResult> list = getDataFiles().get().stream()
33+
.flatMap(it -> ViolationsUtil.readDetails(it).stream())
34+
.toList();
35+
final var byRule = ViolationsUtil.consolidatedFailures(list);
36+
ViolationsUtil.printSummary(byRule, consoleOutput);
37+
consoleOutput.println();
38+
if (list.stream().anyMatch(it -> it.status() != RuleResultStatus.FAIL && it.rule().priority() == Priority.LOW) && !getLogger().isInfoEnabled()) {
39+
consoleOutput.style(StyledTextOutput.Style.Header)
40+
.text("Note: ")
41+
.style(StyledTextOutput.Style.Normal)
42+
.println("In order to see details of LOW priority rules, run build with --info")
43+
.println();
44+
}
45+
ViolationsUtil.printReport(byRule, consoleOutput, getLogger().isInfoEnabled());
46+
}
47+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.netflix.nebula.archrules.gradle
2+
3+
import org.gradle.api.provider.Property
4+
5+
abstract class ArchrulesExtension {
6+
abstract val consoleReportEnabled: Property<Boolean>
7+
}

nebula-archrules-gradle-plugin/src/main/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesRunnerPlugin.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.gradle.api.attributes.Category
88
import org.gradle.api.attributes.Usage
99
import org.gradle.api.plugins.JavaPluginExtension
1010
import org.gradle.internal.extensions.stdlib.capitalized
11+
import org.gradle.kotlin.dsl.create
1112
import org.gradle.kotlin.dsl.getByType
1213
import org.gradle.kotlin.dsl.named
1314
import org.gradle.kotlin.dsl.register
@@ -27,6 +28,8 @@ class ArchrulesRunnerPlugin : Plugin<Project> {
2728
attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling.EXTERNAL))
2829
}
2930
}
31+
val archRulesExt = project.extensions.create<ArchrulesExtension>("archRules")
32+
archRulesExt.consoleReportEnabled.convention(true)
3033
val checkTasks = project.extensions.getByType<JavaPluginExtension>().sourceSets
3134
.map { sourceSet ->
3235
val checkTask =
@@ -80,9 +83,15 @@ class ArchrulesRunnerPlugin : Plugin<Project> {
8083
dependsOn(checkTasks)
8184
}
8285

86+
val consoleReportTask = project.tasks.register<PrintConsoleReportTask>("archRulesConsoleReport") {
87+
getDataFiles().set(dataFiles)
88+
dependsOn(checkTasks)
89+
onlyIf { archRulesExt.consoleReportEnabled.get() }
90+
}
91+
8392
project.tasks.named("check") {
8493
dependsOn(checkTasks)
85-
finalizedBy(jsonReportTask)
94+
finalizedBy(jsonReportTask, consoleReportTask)
8695
}
8796
}
8897
}

nebula-archrules-gradle-plugin/src/main/kotlin/com/netflix/nebula/archrules/gradle/ViolationsUtil.kt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package com.netflix.nebula.archrules.gradle
22

3+
import com.tngtech.archunit.lang.Priority
4+
import org.gradle.internal.logging.text.StyledTextOutput
35
import java.io.File
46
import java.io.FileInputStream
57
import java.io.IOException
68
import java.io.ObjectInputStream
79

10+
/**
11+
* Helpers for dealing with [RuleResult]
12+
*/
813
class ViolationsUtil {
914
companion object {
1015
@JvmStatic
@@ -24,5 +29,67 @@ class ViolationsUtil {
2429
}
2530
return list
2631
}
32+
33+
@JvmStatic
34+
fun printReport(violations: Map<Rule, List<RuleResult>>, output: StyledTextOutput, infoLogging: Boolean) {
35+
violations
36+
.mapValues { it.value.filter { it.rule().priority() != Priority.LOW || infoLogging } }
37+
.filter { it.value.isNotEmpty() }
38+
.forEach { (rule, ruleViolations) ->
39+
val style = when (rule.priority()) {
40+
Priority.LOW -> StyledTextOutput.Style.Normal
41+
Priority.MEDIUM -> StyledTextOutput.Style.Info
42+
Priority.HIGH -> StyledTextOutput.Style.Failure
43+
}
44+
output
45+
.style(StyledTextOutput.Style.Header).text("Rule: ${rule.ruleName} Priority: ")
46+
.style(style)
47+
.println(rule.priority().asString())
48+
.style(style)
49+
.println(rule.description())
50+
ruleViolations.forEach {
51+
output.style(style).println(" " + it.message())
52+
}
53+
output.println()
54+
}
55+
}
56+
57+
@JvmStatic
58+
fun printSummary(results: Map<Rule, List<RuleResult>>, output: StyledTextOutput) {
59+
results.forEach { (rule, results) ->
60+
val failures = results.filter { it.status() != RuleResultStatus.PASS }
61+
if (failures.isEmpty()) {
62+
output.style(StyledTextOutput.Style.Success)
63+
.text(rule.ruleName().padEnd(30))
64+
.text(" ")
65+
.text(rule.priority().asString().padEnd(10))
66+
.println(" (No failures)")
67+
} else {
68+
val style = when (rule.priority()) {
69+
Priority.LOW -> StyledTextOutput.Style.Normal
70+
Priority.MEDIUM -> StyledTextOutput.Style.Info
71+
Priority.HIGH -> StyledTextOutput.Style.Failure
72+
}
73+
output.style(style)
74+
.text(rule.ruleName().padEnd(30))
75+
.text(" ")
76+
.text(rule.priority().asString().padEnd(10))
77+
.println(" (" + failures.size + " failures)")
78+
}
79+
}
80+
}
81+
82+
/**
83+
* Rules which fail due to no match should only count as a failure if they fail for every source set in which that rule was run
84+
*/
85+
@JvmStatic
86+
fun consolidatedFailures(violations: List<RuleResult>): Map<Rule, List<RuleResult>> {
87+
val byType = violations.groupBy { it.rule() }.mapValues { it.value.toSet() }
88+
return byType
89+
.mapValues { (_, fullSet) ->
90+
fullSet.filter { !(it.status() == RuleResultStatus.NO_MATCH && fullSet.size != 1) }
91+
}
92+
.mapValues { it.value.filter { it.status() != RuleResultStatus.PASS } }
93+
}
2794
}
2895
}

nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesLibraryPluginTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ println(moduleMetadataJson)
139139

140140
assertThat(result.task(":archRulesTest"))
141141
.`as`("archRules test task runs")
142-
.hasOutcome(TaskOutcome.SUCCESS)
142+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
143143
assertThat(result)
144144
.hasNoMutableStateWarnings()
145145
.hasNoDeprecationWarnings()

nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesRunnerPluginTest.kt

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,18 @@ class ArchrulesRunnerPluginTest {
7878

7979
assertThat(result.task(":checkArchRulesMain"))
8080
.`as`("archRules run for main source set")
81-
.hasOutcome(TaskOutcome.SUCCESS)
81+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
8282

8383
assertThat(result.task(":checkArchRulesTest"))
8484
.`as`("archRules run for test source set")
85-
.hasOutcome(TaskOutcome.SUCCESS)
85+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
8686

8787
assertThat(result.task(":archRulesJsonReport"))
8888
.`as`("archRules json report runs by default")
89+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
90+
91+
assertThat(result.task(":archRulesConsoleReport"))
92+
.`as`("archRules console report runs by default")
8993
.hasOutcome(TaskOutcome.SUCCESS)
9094

9195
assertThat(result)
@@ -110,6 +114,10 @@ class ArchrulesRunnerPluginTest {
110114
assertThat(jsonReport)
111115
.`as`("json report created")
112116
.exists()
117+
118+
assertThat(result.output)
119+
.contains("ArchRule summary:")
120+
.contains("deprecated LOW (1 failures)")
113121
}
114122

115123
@Test
@@ -148,11 +156,11 @@ class ArchrulesRunnerPluginTest {
148156

149157
assertThat(result.task(":checkArchRulesMain"))
150158
.`as`("archRules run for main source set")
151-
.hasOutcome(TaskOutcome.SUCCESS)
159+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
152160

153161
assertThat(result.task(":checkArchRulesTest"))
154162
.`as`("archRules run for test source set")
155-
.hasOutcome(TaskOutcome.SUCCESS)
163+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
156164

157165
assertThat(result)
158166
.hasNoMutableStateWarnings()
@@ -173,6 +181,63 @@ class ArchrulesRunnerPluginTest {
173181
assertThat(testErrors).hasSize(1)
174182
}
175183

184+
@ParameterizedTest
185+
@EnumSource(SupportedGradleVersion::class)
186+
fun `console report can be disabled`(gradleVersion: SupportedGradleVersion) {
187+
val runner = testProject(projectDir) {
188+
properties {
189+
gradleCache(true)
190+
}
191+
settings {
192+
name("consumer")
193+
}
194+
rootProject {
195+
plugins {
196+
id("java")
197+
id("com.netflix.nebula.archrules.runner")
198+
}
199+
repositories {
200+
mavenCentral()
201+
}
202+
dependencies(
203+
"""archRules("com.netflix.nebula:archrules-deprecation:0.1.+")"""
204+
)
205+
rawBuildScript("""
206+
archRules {
207+
consoleReportEnabled = false
208+
}
209+
"""
210+
)
211+
src {
212+
main {
213+
exampleLibraryClass()
214+
exampleDeprecatedUsage()
215+
}
216+
test {
217+
exampleDeprecatedUsage("FailingCodeTest")
218+
}
219+
}
220+
}
221+
}
222+
223+
val result = runner.run("check", "--stacktrace", "-x", "test"){
224+
withGradleVersion(gradleVersion.version)
225+
forwardOutput()
226+
}
227+
228+
assertThat(result.task(":archRulesConsoleReport"))
229+
.`as`("archRules console report runs by default")
230+
.hasOutcome(TaskOutcome.SKIPPED)
231+
232+
assertThat(result)
233+
.hasNoMutableStateWarnings()
234+
.hasNoDeprecationWarnings()
235+
236+
assertThat(result.output)
237+
.doesNotContain("ArchRule summary:")
238+
.doesNotContain("deprecated LOW (1 failures)")
239+
}
240+
176241
fun readDetails(dataFile: File): List<RuleResult> {
177242
val list: MutableList<RuleResult> = mutableListOf()
178243
try {

nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/IntegrationTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ internal class IntegrationTest {
7070

7171
assertThat(result.task(":code-to-check:checkArchRulesMain"))
7272
.`as`("archRules run for main source set")
73-
.hasOutcome(TaskOutcome.SUCCESS)
73+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
7474

7575
assertThat(result.task(":code-to-check:checkArchRulesTest"))
7676
.`as`("archRules run for test source set")
77-
.hasOutcome(TaskOutcome.SUCCESS)
77+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
7878

7979
assertThat(result.task(":code-to-check:check"))
8080
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE)

0 commit comments

Comments
 (0)