Skip to content

Commit 1564171

Browse files
committed
automatically register rule service providers in META-INF
1 parent 37e726e commit 1564171

File tree

11 files changed

+139
-43
lines changed

11 files changed

+139
-43
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ public class LibraryArchRules implements ArchRulesService {
6161
}
6262
```
6363

64-
You will also need to create a file at `src/archRules/resources/META-INF/services/com.netflix.nebula.archrules.core.ArchRulesService` and add the fully qualified name of your rules class to that file.
65-
6664
When authoring rules about the usage of your own library code, it is recommended to colocate your rules library in the
6765
same project as the library code. The ArchRules plugin will publish the rules in a separate Jar, and the Runner plugin
6866
will select that jar for running rules, but these rule classes will not end up in the runtime classpath.
@@ -143,7 +141,8 @@ archRules {
143141

144142
## How it works
145143

146-
The Archrules Library plugin produces a separate Jar for the `archRules` sourceset, which is exposed as an alternate variant of the library.
144+
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.
145+
The Archrules Runner plugin uses a Java [ServiceLoader](https://devdocs.io/openjdk~25/java.base/java/util/serviceloader) to discover all implementations of `com.netflix.nebula.archrules.core.ArchRulesService` in the rule libraries.
147146

148147
## LICENSE
149148

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
org.gradle.caching=true
22
org.gradle.configuration-cache=true
33
systemProp.nebula.features.coreLockingSupport=true
4+
nebula.integTest=false

nebula-archrules-core/gradle.lockfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# This is a Gradle generated file for dependency locking.
22
# Manual edits can break the build and are not advised.
33
# This file is expected to be part of source control.
4-
com.tngtech.archunit:archunit:1.4.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
4+
com.netflix.nebula:archrules-deprecation:0.1.2=archRules
5+
com.tngtech.archunit:archunit:1.4.1=archRules,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
56
net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testRuntimeClasspath
67
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
78
org.assertj:assertj-core:3.27.6=testCompileClasspath,testRuntimeClasspath
@@ -14,5 +15,5 @@ org.junit.platform:junit-platform-engine:1.12.2=testRuntimeClasspath
1415
org.junit.platform:junit-platform-launcher:1.12.2=testRuntimeClasspath
1516
org.junit:junit-bom:5.12.2=testCompileClasspath,testRuntimeClasspath
1617
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
17-
org.slf4j:slf4j-api:2.0.17=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
18+
org.slf4j:slf4j-api:2.0.17=archRules,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
1819
empty=annotationProcessor,testAnnotationProcessor

nebula-archrules-gradle-plugin/gradle.lockfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
cglib:cglib-nodep:3.2.2=integTestRuntimeClasspath,testRuntimeClasspath
55
com.fasterxml.jackson.core:jackson-annotations:2.20=compileClasspath,implementationDependenciesMetadata,integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
66
com.jayway.jsonpath:json-path:2.9.0=integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
7+
com.netflix.nebula:archrules-deprecation:0.1.2=archRules
8+
com.netflix.nebula:archrules-joda:0.1.2=archRules
9+
com.netflix.nebula:archrules-testing-frameworks:0.1.2=archRules
10+
com.netflix.nebula:nebula-archrules-core:0.1.5=archRules
711
com.netflix.nebula:nebula-test:11.8.0=integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
8-
com.tngtech.archunit:archunit:1.4.1=compileClasspath,implementationDependenciesMetadata,integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
12+
com.tngtech.archunit:archunit:1.4.1=archRules,compileClasspath,implementationDependenciesMetadata,integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
913
io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation
1014
net.bytebuddy:byte-buddy:1.17.7=integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
1115
net.javacrumbs.json-unit:json-unit-assertj:5.0.0=integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
@@ -52,7 +56,7 @@ org.junit:junit-bom:5.14.0=integTestCompileClasspath,integTestImplementationDepe
5256
org.objenesis:objenesis:2.4=integTestRuntimeClasspath,testRuntimeClasspath
5357
org.opentest4j:opentest4j:1.3.0=integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
5458
org.ow2.asm:asm:9.3=integTestRuntimeClasspath,testRuntimeClasspath
55-
org.slf4j:slf4j-api:2.0.17=compileClasspath,implementationDependenciesMetadata,integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
59+
org.slf4j:slf4j-api:2.0.17=archRules,compileClasspath,implementationDependenciesMetadata,integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
5660
tools.jackson.core:jackson-core:3.0.2=compileClasspath,implementationDependenciesMetadata,integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
5761
tools.jackson.core:jackson-databind:3.0.2=compileClasspath,implementationDependenciesMetadata,integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
5862
tools.jackson:jackson-bom:3.0.2=compileClasspath,implementationDependenciesMetadata,integTestCompileClasspath,integTestImplementationDependenciesMetadata,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.netflix.nebula.archrules.gradle;
2+
3+
import com.tngtech.archunit.thirdparty.org.objectweb.asm.ClassVisitor;
4+
5+
import java.util.ArrayList;
6+
import java.util.Arrays;
7+
import java.util.List;
8+
9+
import static com.tngtech.archunit.thirdparty.org.objectweb.asm.Opcodes.ASM9;
10+
11+
class ArchRulesServiceVisitor extends ClassVisitor {
12+
public List<String> getArchRuleServiceClasses() {
13+
return archRuleServiceClasses;
14+
}
15+
16+
private final List<String> archRuleServiceClasses = new ArrayList<>();
17+
18+
ArchRulesServiceVisitor() {
19+
super(ASM9);
20+
}
21+
22+
public void visit(int version, int access, String name,
23+
String signature, String superName, String[] interfaces) {
24+
if (Arrays.asList(interfaces).contains("com/netflix/nebula/archrules/core/ArchRulesService")) {
25+
archRuleServiceClasses.add(name.replace("/", "."));
26+
}
27+
}
28+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.netflix.nebula.archrules.gradle;
2+
3+
import com.tngtech.archunit.thirdparty.org.objectweb.asm.ClassReader;
4+
import org.gradle.api.DefaultTask;
5+
import org.gradle.api.file.ConfigurableFileCollection;
6+
import org.gradle.api.provider.Property;
7+
import org.gradle.api.tasks.*;
8+
import org.jspecify.annotations.NonNull;
9+
10+
import java.io.File;
11+
import java.io.IOException;
12+
import java.nio.file.Files;
13+
import java.nio.file.StandardOpenOption;
14+
15+
/**
16+
* Generates a file in META-INF/services to allow Rule classes to be discovered by the Runner
17+
*/
18+
@CacheableTask
19+
abstract public class GenerateServicesRegistryTask extends DefaultTask {
20+
/**
21+
* The classes declared in the archRules source set
22+
*/
23+
@InputFiles
24+
@PathSensitive(PathSensitivity.RELATIVE)
25+
abstract public ConfigurableFileCollection getRuleSourceClasses();
26+
27+
/**
28+
* The file in META-INF/services to output to. It should be named com.netflix.nebula.archrules.core.ArchRulesService.
29+
*/
30+
@OutputFile
31+
abstract public Property<@NonNull File> getArchRuleServicesFile();
32+
33+
@TaskAction
34+
public void generate() throws IOException {
35+
ArchRulesServiceVisitor visitor = new ArchRulesServiceVisitor();
36+
getRuleSourceClasses().getAsFileTree().getFiles()
37+
.stream()
38+
.filter(it -> it.getName().endsWith(".class"))
39+
.forEach(classFile -> {
40+
try {
41+
if(getLogger().isDebugEnabled()) {
42+
getLogger().debug("Generating archive rules for {}", classFile.getName());
43+
}
44+
ClassReader cr = new ClassReader(Files.newInputStream(classFile.toPath(), StandardOpenOption.READ));
45+
cr.accept(visitor, ClassReader.SKIP_DEBUG);
46+
} catch (IOException e) {
47+
getLogger().warn("Failed to read class file {}", classFile.getAbsolutePath(), e);
48+
}
49+
});
50+
51+
getArchRuleServicesFile().get().createNewFile();
52+
String fileContent = String.join("\n", visitor.getArchRuleServiceClasses());
53+
Files.writeString(getArchRuleServicesFile().get().toPath(), fileContent, StandardOpenOption.WRITE);
54+
}
55+
}

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,33 @@ class ArchrulesLibraryPlugin : Plugin<Project> {
2626
override fun apply(project: Project) {
2727
val version = determineVersion()
2828
project.pluginManager.withPlugin("java") {
29-
val ext = project.extensions.getByType<JavaPluginExtension>()
30-
val archRulesSourceSet = ext.sourceSets.create("archRules")
29+
val javaExt = project.extensions.getByType<JavaPluginExtension>()
30+
val archRulesSourceSet = javaExt.sourceSets.create("archRules")
3131
project.dependencies.add(
3232
archRulesSourceSet.implementationConfigurationName,
3333
"com.netflix.nebula:nebula-archrules-core:$version"
3434
)
35+
val generateServicesTask = project.tasks.register<GenerateServicesRegistryTask>("generateServicesRegistry"){
36+
archRuleServicesFile.set(
37+
project.layout.buildDirectory.file(
38+
"resources/archRules/META-INF/services/com.netflix.nebula.archrules.core.ArchRulesService"
39+
).map { it.asFile }
40+
)
41+
ruleSourceClasses.setFrom(archRulesSourceSet.output)
42+
dependsOn(archRulesSourceSet.classesTaskName)
43+
}
44+
project.tasks.named(archRulesSourceSet.classesTaskName){
45+
finalizedBy(generateServicesTask)
46+
}
47+
project.tasks.named("processArchRulesResources"){
48+
finalizedBy(generateServicesTask)
49+
}
3550
val jarTask = project.tasks.register<Jar>("archRulesJar") {
3651
description = "Assembles a jar archive containing the classes of the arch rules."
3752
group = "build"
3853
from(archRulesSourceSet.output)
3954
archiveClassifier.set("arch-rules")
55+
dependsOn(generateServicesTask)
4056
}
4157
registerRuntimeFeatureForSourceSet(project, archRulesSourceSet, jarTask)
4258
project.pluginManager.withPlugin("jvm-test-suite") {
@@ -49,6 +65,16 @@ class ArchrulesLibraryPlugin : Plugin<Project> {
4965
implementation(archRulesSourceSet.output)
5066
implementation("com.netflix.nebula:nebula-archrules-core:$version")
5167
}
68+
javaExt.sourceSets.named("archRulesTest").configure {
69+
project.tasks.named(compileJavaTaskName){
70+
dependsOn(generateServicesTask)
71+
}
72+
project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
73+
project.tasks.named(getCompileTaskName("kotlin")) {
74+
dependsOn(generateServicesTask)
75+
}
76+
}
77+
}
5278
}
5379
}
5480
project.tasks.named("check") {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,18 @@ class ArchrulesLibraryPluginTest {
7171
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
7272
assertThat(result.task(":archRulesJar"))
7373
.hasOutcome(TaskOutcome.SUCCESS)
74+
assertThat(result.task(":generateServicesRegistry"))
75+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
7476
assertThat(result)
7577
.hasNoMutableStateWarnings()
7678
.hasNoDeprecationWarnings()
7779

80+
val serviceFile = projectDir.resolve("build/resources/archRules/META-INF/services/com.netflix.nebula.archrules.core.ArchRulesService")
81+
assertThat(serviceFile)
82+
.`as`("service file is created")
83+
.exists()
84+
.content().contains("com.example.library.LibraryArchRules")
85+
7886
assertThat(projectDir.resolve("build/libs/library-with-rules-0.0.1.jar"))
7987
.`as`("Library Jar is created")
8088
.exists()

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

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,9 @@
11
package com.netflix.nebula.archrules.gradle
22

3+
import nebula.test.dsl.*
34
import nebula.test.dsl.TestKitAssertions.assertThat
4-
import nebula.test.dsl.main
5-
import nebula.test.dsl.plugins
6-
import nebula.test.dsl.properties
7-
import nebula.test.dsl.repositories
8-
import nebula.test.dsl.rootProject
9-
import nebula.test.dsl.run
10-
import nebula.test.dsl.settings
11-
import nebula.test.dsl.sourceSet
12-
import nebula.test.dsl.src
13-
import nebula.test.dsl.test
14-
import nebula.test.dsl.testProject
15-
import net.javacrumbs.jsonunit.assertj.assertThatJson
165
import org.gradle.testfixtures.ProjectBuilder
176
import org.gradle.testkit.runner.TaskOutcome
18-
import org.junit.jupiter.api.Disabled
197
import org.junit.jupiter.api.Test
208
import org.junit.jupiter.api.io.TempDir
219
import org.junit.jupiter.params.ParameterizedTest
@@ -71,7 +59,7 @@ class ArchrulesRunnerPluginTest {
7159
}
7260
}
7361

74-
val result = runner.run("check", "--stacktrace", "-x", "test"){
62+
val result = runner.run("check", "--stacktrace", "-x", "test") {
7563
withGradleVersion(gradleVersion.version)
7664
forwardOutput()
7765
}
@@ -202,7 +190,8 @@ class ArchrulesRunnerPluginTest {
202190
dependencies(
203191
"""archRules("com.netflix.nebula:archrules-deprecation:0.1.+")"""
204192
)
205-
rawBuildScript("""
193+
rawBuildScript(
194+
"""
206195
archRules {
207196
consoleReportEnabled = false
208197
}
@@ -220,7 +209,7 @@ archRules {
220209
}
221210
}
222211

223-
val result = runner.run("check", "--stacktrace", "-x", "test"){
212+
val result = runner.run("check", "--stacktrace", "-x", "test") {
224213
withGradleVersion(gradleVersion.version)
225214
forwardOutput()
226215
}

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,6 @@ internal class IntegrationTest {
5555
}
5656
}
5757

58-
val serviceFile = projectDir
59-
.resolve("library-with-rules/src/archRules/resources/META-INF/services/com.netflix.nebula.archrules.core.ArchRulesService")
60-
serviceFile.parentFile.mkdirs()
61-
serviceFile.writeText("com.example.library.LibraryArchRules")
62-
6358
val result = runner.run("check", "--stacktrace") {
6459
withGradleVersion(gradleVersion.version)
6560
forwardOutput()
@@ -135,11 +130,6 @@ internal class IntegrationTest {
135130
}
136131
}
137132

138-
val serviceFile = projectDir
139-
.resolve("library-with-rules/src/archRules/resources/META-INF/services/com.netflix.nebula.archrules.core.ArchRulesService")
140-
serviceFile.parentFile.mkdirs()
141-
serviceFile.writeText("com.example.library.LibraryArchRules")
142-
143133
val result = runner.run("check", "--stacktrace") {
144134
withGradleVersion(gradleVersion.version)
145135
forwardOutput()
@@ -214,11 +204,6 @@ internal class IntegrationTest {
214204
}
215205
}
216206

217-
val serviceFile = projectDir
218-
.resolve("library-with-rules/src/archRules/resources/META-INF/services/com.netflix.nebula.archrules.core.ArchRulesService")
219-
serviceFile.parentFile.mkdirs()
220-
serviceFile.writeText("com.example.library.LibraryArchRules")
221-
222207
val result = runner.run("check", "--stacktrace") {
223208
withGradleVersion(gradleVersion.version)
224209
forwardOutput()

0 commit comments

Comments
 (0)