Skip to content

Commit 23d3195

Browse files
ArchrulesLibraryPlugin archRules compilation
add archRules source set to ArchRules library projects which can be compiled add ArchRules core library as a dependency for the archRules sourceSet Co-authored-by: Emily Yuan <eyuan@netflix.com>
1 parent 53cd7dc commit 23d3195

File tree

4 files changed

+136
-4
lines changed

4 files changed

+136
-4
lines changed

README.md

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,68 @@
1+
# Nebula ArchRules
2+
3+
[ArchUnit](https://www.archunit.org/) a popular OSS library used to enforce “architectural” code rules as part of a
4+
JUnit suite. However, it is limited by its design to be used as part of a JUnit suite in a single repository. Nebula
5+
ArchRules is a toolkit which gives organizations the ability to share and apply rules across any number of repositories.
6+
Rules can be sourced from OSS libraries or private internal libraries.
7+
8+
### Authoring Rules
9+
10+
To author rules, apply the ArchRules Library plugin to a project:
11+
12+
```kotlin
13+
plugins {
14+
id("com.netflix.nebula.archrules.library") version ("latest.release")
15+
}
16+
```
17+
18+
This plugin will create a source set called `archRules`. Create classes in that source set which implement the
19+
`com.netflix.nebula.archrules.core.ArchRulesService` interface.
20+
21+
#### Example
22+
23+
```java
24+
package com.example.library;
25+
26+
import com.netflix.nebula.archrules.core.ArchRulesService;
27+
import com.tngtech.archunit.lang.ArchRule;
28+
import com.tngtech.archunit.lang.Priority;
29+
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
30+
31+
import java.util.Map;
32+
33+
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target;
34+
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.targetOwner;
35+
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith;
36+
37+
public class LibraryArchRules implements ArchRulesService {
38+
private final ArchRule noDeprecated = ArchRuleDefinition.priority(Priority.LOW).noClasses()
39+
.should().accessTargetWhere(targetOwner(annotatedWith(Deprecated.class)))
40+
.orShould().accessTargetWhere(target(annotatedWith(Deprecated.class)))
41+
.orShould().dependOnClassesThat().areAnnotatedWith(Deprecated.class)
42+
.allowEmptyShould(true)
43+
.as("No code should reference deprecated APIs")
44+
.because("usage of deprecated APIs introduces risk that future upgrades and migrations will be blocked");
45+
46+
@Override
47+
public Map<String, ArchRule> getRules() {
48+
return Map.of("deprecated", noDeprecated);
49+
}
50+
}
51+
```
52+
53+
When authoring rules about the usage of your own library code, it is recommended to colocate your rules library in the
54+
same project as the library code. The ArchRules plugin will publish the rules in a separate Jar, and the Runner plugin
55+
will select that jar for running rules, but these rule classes will not end up up in the runtime classpath.
56+
157
## LICENSE
58+
259
Copyright 2025 Netflix, Inc.
360

4-
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
61+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
62+
License. You may obtain a copy of the License at
563

664
http://www.apache.org/licenses/LICENSE-2.0
765

8-
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
66+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
67+
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
68+
language governing permissions and limitations under the License.

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,18 @@ package com.netflix.nebula.archrules.gradle
22

33
import org.gradle.api.Plugin
44
import org.gradle.api.Project
5+
import org.gradle.api.plugins.JavaPluginExtension
6+
import org.gradle.kotlin.dsl.getByType
57

68
class ArchrulesLibraryPlugin : Plugin<Project> {
7-
override fun apply(target: Project) {
9+
override fun apply(project: Project) {
10+
project.plugins.withId("java") {
11+
val ext = project.extensions.getByType<JavaPluginExtension>()
12+
val archRulesSourceSet = ext.sourceSets.create("archRules")
13+
val version = ArchrulesLibraryPlugin::class.java.`package`.implementationVersion ?: "latest.release"
14+
project.dependencies.add(archRulesSourceSet.implementationConfigurationName,
15+
"com.netflix.nebula:nebula-archrules-core:$version"
16+
)
17+
}
818
}
919
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.netflix.nebula.archrules.gradle
2+
3+
import nebula.test.dsl.TestKitAssertions.assertThat
4+
import org.gradle.testfixtures.ProjectBuilder
5+
import org.junit.jupiter.api.Test
6+
7+
class ArchrulesLibraryPluginTest {
8+
9+
@Test
10+
fun `plugin registers library dependency`() {
11+
val project = ProjectBuilder.builder().build()
12+
project.plugins.apply("java")
13+
project.plugins.apply(ArchrulesLibraryPlugin::class.java)
14+
val configuration = project.configurations.findByName("archRulesImplementation")
15+
assertThat(configuration).isNotNull
16+
val coreLibrary = configuration!!.dependencies
17+
.firstOrNull { it.group == "com.netflix.nebula" && it.name == "nebula-archrules-core" }
18+
assertThat(coreLibrary).isNotNull
19+
assertThat(coreLibrary!!.version).isEqualTo("latest.release")
20+
}
21+
}

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

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ internal class IntegrationTest {
2020
id("java-library")
2121
id("com.netflix.nebula.archrules.library")
2222
}
23+
repositories {
24+
maven("https://netflixoss.jfrog.io/artifactory/gradle-plugins")
25+
mavenCentral()
26+
}
2327
src {
2428
main {
2529
java(
@@ -34,6 +38,40 @@ public class LibraryClass {
3438
"""
3539
)
3640
}
41+
sourceSet("archRules") {
42+
java(
43+
"com/example/library/LibraryArchRules.java",
44+
//language=java
45+
"""
46+
package com.example.library;
47+
48+
import com.netflix.nebula.archrules.core.ArchRulesService;
49+
import com.tngtech.archunit.lang.ArchRule;
50+
import com.tngtech.archunit.lang.Priority;
51+
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
52+
import java.util.Map;
53+
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target;
54+
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.targetOwner;
55+
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith;
56+
57+
public class LibraryArchRules implements ArchRulesService {
58+
private final ArchRule noDeprecated = ArchRuleDefinition.priority(Priority.LOW)
59+
.noClasses()
60+
.should().accessTargetWhere(targetOwner(annotatedWith(Deprecated.class)))
61+
.orShould().accessTargetWhere(target(annotatedWith(Deprecated.class)))
62+
.orShould().dependOnClassesThat().areAnnotatedWith(Deprecated.class)
63+
.allowEmptyShould(true)
64+
.as("No code should reference deprecated APIs")
65+
.because("usage of deprecated APIs introduces risk that future upgrades and migrations will be blocked");
66+
67+
@Override
68+
public Map<String, ArchRule> getRules() {
69+
return Map.of("deprecated", noDeprecated);
70+
}
71+
}
72+
"""
73+
)
74+
}
3775
}
3876
}
3977
subProject("code-to-check") {
@@ -48,8 +86,11 @@ public class LibraryClass {
4886
}
4987
}
5088

51-
val result = runner.run("check")
89+
val result = runner.run("check", "compileArchRulesJava")
5290

91+
assertThat(result.task(":library-with-rules:compileArchRulesJava"))
92+
.`as`("compile task runs for the archRules source set")
93+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE)
5394
assertThat(result.task(":library-with-rules:check"))
5495
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE)
5596
assertThat(result.task(":code-to-check:check"))

0 commit comments

Comments
 (0)