Skip to content

Commit 7fdef78

Browse files
committed
add aggregate console report
1 parent 131f4d1 commit 7fdef78

File tree

7 files changed

+281
-6
lines changed

7 files changed

+281
-6
lines changed

nebula-archrules-gradle-plugin/build.gradle.kts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ gradlePlugin {
2626
description = "Sets up a project to consume archrules libraries and run them against the code in the current project"
2727
tags.addAll("nebula", "archunit")
2828
}
29+
create("aggregate") {
30+
id = "com.netflix.nebula.archrules.aggregate"
31+
implementationClass = "com.netflix.nebula.archrules.gradle.ArchrulesAggregateConsoleReportPlugin"
32+
displayName = "ArchRules Aggregate Console Report Plugin"
33+
description = "Consolidates console reports for multiple subprojects"
34+
tags.addAll("nebula", "archunit")
35+
}
2936
}
3037
}
3138
java {
@@ -39,7 +46,7 @@ testing {
3946
useJUnitJupiter()
4047
targets.all {
4148
testTask.configure {
42-
maxParallelForks = 2
49+
maxParallelForks = 4
4350
}
4451
}
4552
}
@@ -69,4 +76,4 @@ configurations.named("mainArchRulesRuntime").configure {
6976
}
7077
}
7178
}
72-
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.netflix.nebula.archrules.gradle
2+
3+
import com.tngtech.archunit.lang.Priority
4+
import org.gradle.api.Plugin
5+
import org.gradle.api.Project
6+
import org.gradle.api.attributes.Category
7+
import org.gradle.api.attributes.VerificationType
8+
import org.gradle.kotlin.dsl.named
9+
import org.gradle.kotlin.dsl.project
10+
import org.gradle.kotlin.dsl.register
11+
12+
class ArchrulesAggregateConsoleReportPlugin : Plugin<Project> {
13+
override fun apply(project: Project) {
14+
val ext = project.extensions.create("archrulesAggregate", ArchrulesAggregateExtension::class.java)
15+
ext.skipPassingSummaries.set(false)
16+
ext.consoleDetailsThreshold(Priority.MEDIUM)
17+
val archRulesAggregateDependencies = project.configurations.dependencyScope("archRulesAggregateDependencies") {
18+
description = "projects to collect archrules data from"
19+
}
20+
val archRulesDataFiles = project.configurations.resolvable("archRulesDataFiles") {
21+
extendsFrom(archRulesAggregateDependencies.get())
22+
attributes {
23+
attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.VERIFICATION))
24+
attribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE, project.objects.named("arch-rules"))
25+
}
26+
}
27+
project.subprojects {
28+
project.dependencies.add("archRulesAggregateDependencies", project.dependencies.project(":$name"))
29+
}
30+
project.tasks.register<PrintConsoleReportTask>("archRulesAggregateConsoleReport") {
31+
dataFiles.from(archRulesDataFiles.map {
32+
it.incoming.artifactView {
33+
lenient(true) // to handle the case where a subproject doesn't have archrules runner
34+
}.files
35+
})
36+
summaryForPassingDisabled.set(ext.skipPassingSummaries)
37+
detailsThreshold.set(ext.consoleDetailsThreshold)
38+
}
39+
}
40+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.netflix.nebula.archrules.gradle
2+
3+
import com.tngtech.archunit.lang.Priority
4+
import org.gradle.api.Action
5+
import org.gradle.api.provider.ListProperty
6+
import org.gradle.api.provider.MapProperty
7+
import org.gradle.api.provider.Property
8+
9+
/**
10+
* Settings for the aggregate console report
11+
*/
12+
abstract class ArchrulesAggregateExtension {
13+
14+
/**
15+
* Skip printing lines in the console report summary for passing rules
16+
*/
17+
abstract val skipPassingSummaries: Property<Boolean>
18+
abstract val consoleDetailsThreshold: Property<Priority>
19+
20+
fun consoleDetailsThreshold(priority: Priority) {
21+
consoleDetailsThreshold.set(priority)
22+
}
23+
24+
fun consoleDetailsThreshold(priority: String) {
25+
consoleDetailsThreshold.set(Priority.valueOf(priority))
26+
}
27+
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import com.netflix.nebula.archrules.gradle.ArchRuleAttribute.ARCH_RULES
44
import com.tngtech.archunit.lang.Priority
55
import org.gradle.api.Plugin
66
import org.gradle.api.Project
7+
import org.gradle.api.artifacts.type.ArtifactTypeDefinition
78
import org.gradle.api.attributes.Bundling
89
import org.gradle.api.attributes.Category
910
import org.gradle.api.attributes.Usage
11+
import org.gradle.api.attributes.VerificationType
12+
import org.gradle.api.component.AdhocComponentWithVariants
1013
import org.gradle.api.plugins.JavaPluginExtension
14+
import org.gradle.api.plugins.internal.JavaConfigurationVariantMapping
1115
import org.gradle.api.tasks.SourceSet
1216
import org.gradle.internal.extensions.stdlib.capitalized
1317
import org.gradle.kotlin.dsl.add
@@ -54,7 +58,7 @@ class ArchrulesRunnerPlugin : Plugin<Project> {
5458
}
5559

5660
val consoleReportTask = project.tasks.register<PrintConsoleReportTask>("archRulesConsoleReport") {
57-
dataFiles.set(
61+
dataFiles.from(
5862
project.provider { (project.tasks.withType<CheckRulesTask>().flatMap { it.outputs.files }) }
5963
)
6064
summaryForPassingDisabled.set(archRulesExt.skipPassingSummaries)
@@ -63,6 +67,20 @@ class ArchrulesRunnerPlugin : Plugin<Project> {
6367
onlyIf { archRulesExt.consoleReportEnabled.get() }
6468
}
6569

70+
project.configurations.consumable("archRulesReportElements") {
71+
description = "Report data for ArchRules"
72+
outgoing.artifacts(
73+
project.provider { (project.tasks.withType<CheckRulesTask>().flatMap { it.outputs.files }) }
74+
){
75+
type = ArtifactTypeDefinition.BINARY_DATA_TYPE
76+
builtBy(project.tasks.withType<CheckRulesTask>())
77+
}
78+
attributes {
79+
attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.VERIFICATION))
80+
attribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE, project.objects.named("arch-rules"))
81+
}
82+
}
83+
6684
val enforceTask = project.tasks.register<EnforceArchRulesTask>("enforceArchRules") {
6785
dependsOn(checkTasks)
6886
dataFiles.set(

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.netflix.nebula.archrules.gradle
22

33
import com.tngtech.archunit.lang.Priority
44
import org.gradle.api.DefaultTask
5+
import org.gradle.api.file.ConfigurableFileCollection
56
import org.gradle.api.provider.ListProperty
67
import org.gradle.api.provider.Property
78
import org.gradle.api.tasks.*
@@ -22,7 +23,7 @@ abstract class PrintConsoleReportTask : DefaultTask() {
2223
*/
2324
@get:InputFiles
2425
@get:PathSensitive(PathSensitivity.RELATIVE)
25-
abstract val dataFiles: ListProperty<File>
26+
abstract val dataFiles: ConfigurableFileCollection
2627

2728
/**
2829
* if summary lines for passing rules should print
@@ -40,7 +41,7 @@ abstract class PrintConsoleReportTask : DefaultTask() {
4041
@TaskAction
4142
fun printReport() {
4243
val consoleOutput = services.get<StyledTextOutputFactory>().create("archrules")
43-
val list = dataFiles.get()
44+
val list = dataFiles.files
4445
.filter(File::exists)
4546
.flatMap { ViolationsUtil.readDetails(it) }
4647
.toList()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package com.netflix.nebula.archrules.gradle
2+
3+
import com.tngtech.archunit.lang.Priority
4+
import nebula.test.dsl.TestKitAssertions.assertThat
5+
import nebula.test.dsl.TestProjectBuilder
6+
import nebula.test.dsl.main
7+
import nebula.test.dsl.plugins
8+
import nebula.test.dsl.properties
9+
import nebula.test.dsl.repositories
10+
import nebula.test.dsl.rootProject
11+
import nebula.test.dsl.run
12+
import nebula.test.dsl.src
13+
import nebula.test.dsl.subProject
14+
import nebula.test.dsl.testProject
15+
import org.gradle.kotlin.dsl.findByType
16+
import org.gradle.testfixtures.ProjectBuilder
17+
import org.junit.jupiter.api.Test
18+
import org.junit.jupiter.api.io.TempDir
19+
import java.io.File
20+
21+
class ArchrulesAggregateConsoleReportPluginTest {
22+
@TempDir
23+
lateinit var projectDir: File
24+
25+
private fun TestProjectBuilder.setup(withFailures : Boolean = true) {
26+
properties {
27+
buildCache(true)
28+
}
29+
rootProject {
30+
plugins {
31+
id("com.netflix.nebula.archrules.aggregate")
32+
}
33+
}
34+
subProject("library") {
35+
plugins {
36+
id("java")
37+
}
38+
src {
39+
main {
40+
exampleLibraryClass()
41+
}
42+
}
43+
}
44+
subProject("sub1") {
45+
plugins {
46+
id("java")
47+
id("com.netflix.nebula.archrules.runner")
48+
}
49+
repositories {
50+
mavenCentral()
51+
}
52+
dependencies(
53+
"""implementation(project(":library"))""",
54+
"""archRules("com.netflix.nebula:archrules-deprecation:0.+")"""
55+
)
56+
src {
57+
main {
58+
if(withFailures) {
59+
exampleDeprecatedUsage("FailingCode1")
60+
}
61+
}
62+
}
63+
}
64+
subProject("sub2") {
65+
plugins {
66+
id("java")
67+
id("com.netflix.nebula.archrules.runner")
68+
}
69+
repositories{
70+
mavenCentral()
71+
}
72+
dependencies(
73+
"""implementation(project(":library"))""",
74+
"""archRules("com.netflix.nebula:archrules-deprecation:0.+")"""
75+
)
76+
src {
77+
main {
78+
if(withFailures) {
79+
exampleDeprecatedUsage("FailingCode2")
80+
}
81+
}
82+
}
83+
}
84+
}
85+
86+
@Test
87+
fun `settings defaults`() {
88+
val project = ProjectBuilder.builder().build()
89+
project.plugins.apply("java")
90+
project.plugins.apply(ArchrulesAggregateConsoleReportPlugin::class.java)
91+
val extension = project.extensions.findByType<ArchrulesAggregateExtension>()!!
92+
assertThat(extension.skipPassingSummaries.get()).isFalse()
93+
assertThat(extension.consoleDetailsThreshold.get()).isEqualTo(Priority.MEDIUM)
94+
}
95+
96+
@Test
97+
fun test() {
98+
val runner = testProject(projectDir) {
99+
setup()
100+
}
101+
val result = runner.run("archRulesAggregateConsoleReport") {
102+
forwardOutput()
103+
}
104+
assertThat(result.output)
105+
.contains("deprecatedForRemoval MEDIUM (2 failures)")
106+
.contains("deprecated LOW (4 failures)")
107+
}
108+
109+
@Test
110+
fun `test passing skip`() {
111+
val runner = testProject(projectDir) {
112+
setup(withFailures = false)
113+
rootProject{
114+
rawBuildScript("""
115+
archrulesAggregate {
116+
skipPassingSummaries = true
117+
}
118+
"""
119+
)
120+
}
121+
}
122+
val result = runner.run("archRulesAggregateConsoleReport") {
123+
forwardOutput()
124+
}
125+
assertThat(result.output)
126+
.doesNotContain("deprecatedForRemoval")
127+
.doesNotContain("deprecated")
128+
}
129+
130+
131+
@Test
132+
fun `test details threshold`() {
133+
val runner = testProject(projectDir) {
134+
setup()
135+
rootProject{
136+
rawBuildScript("""
137+
archrulesAggregate {
138+
consoleDetailsThreshold("LOW")
139+
}
140+
"""
141+
)
142+
}
143+
}
144+
val result = runner.run("archRulesAggregateConsoleReport") {
145+
forwardOutput()
146+
}
147+
assertThat(result.output)
148+
.contains("Method <com.example.consumer.FailingCode1.aMethod()> calls method <com.example.library.LibraryClass.deprecatedApi()>")
149+
}
150+
}

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class ArchrulesRunnerPluginTest {
8484
project.plugins.apply("java")
8585
project.plugins.apply(ArchrulesRunnerPlugin::class.java)
8686
val consoleReport = project.tasks.named<PrintConsoleReportTask>("archRulesConsoleReport")
87-
assertThat(consoleReport.get().dataFiles.get())
87+
assertThat(consoleReport.get().dataFiles.files)
8888
.`as`("console report inputs are correct")
8989
.hasSize(2)
9090
val jsonReport = project.tasks.named<PrintJsonReportTask>("archRulesJsonReport")
@@ -165,6 +165,26 @@ class ArchrulesRunnerPluginTest {
165165
.contains("Note: In order to see details of rules with priority less than MEDIUM,")
166166
}
167167

168+
@ParameterizedTest
169+
@EnumSource(SupportedGradleVersion::class)
170+
fun `plugin produces outgoing variants for reports`(gradleVersion: SupportedGradleVersion) {
171+
val runner = testProject(projectDir) {
172+
setupConsumerProject()
173+
}
174+
175+
val result = runner.run("outgoingVariants") {
176+
withGradleVersion(gradleVersion.version)
177+
forwardOutput()
178+
}
179+
180+
containsInOrder(result.output,
181+
"Variant archRulesReportElements",
182+
"- org.gradle.category = verification",
183+
"- org.gradle.verificationtype = arch-rules",
184+
"- build/reports/archrules/main.data (artifactType = binary)",
185+
"- build/reports/archrules/test.data (artifactType = binary)")
186+
}
187+
168188
@Test
169189
fun `plugin checks each sourceset from its runtime`() {
170190
val runner = testProject(projectDir) {
@@ -600,4 +620,16 @@ archRules {
600620
assertThat(deprecationResult).isNotNull
601621
assertThat(deprecationResult!!.rule.priority).isEqualTo(Priority.LOW)
602622
}
623+
624+
private fun containsInOrder(actual: String, vararg expected: String) {
625+
var i = 0
626+
for (e in expected) {
627+
assertThat(actual).contains(e)
628+
val newIndex = actual.indexOf(e, i)
629+
assertThat(newIndex)
630+
.`as`("$e found at $newIndex but should be after $i")
631+
.isGreaterThan(i)
632+
i = newIndex
633+
}
634+
}
603635
}

0 commit comments

Comments
 (0)