Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 62 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,68 @@
# Nebula ArchRules

[ArchUnit](https://www.archunit.org/) a popular OSS library used to enforce “architectural” code rules as part of a
JUnit suite. However, it is limited by its design to be used as part of a JUnit suite in a single repository. Nebula
ArchRules is a toolkit which gives organizations the ability to share and apply rules across any number of repositories.
Rules can be sourced from OSS libraries or private internal libraries.

### Authoring Rules

To author rules, apply the ArchRules Library plugin to a project:

```kotlin
plugins {
id("com.netflix.nebula.archrules.library") version ("latest.release")
}
```

This plugin will create a source set called `archRules`. Create classes in that source set which implement the
`com.netflix.nebula.archrules.core.ArchRulesService` interface.

#### Example

```java
package com.example.library;

import com.netflix.nebula.archrules.core.ArchRulesService;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.Priority;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;

import java.util.Map;

import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target;
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.targetOwner;
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith;

public class LibraryArchRules implements ArchRulesService {
private final ArchRule noDeprecated = ArchRuleDefinition.priority(Priority.LOW).noClasses()
.should().accessTargetWhere(targetOwner(annotatedWith(Deprecated.class)))
.orShould().accessTargetWhere(target(annotatedWith(Deprecated.class)))
.orShould().dependOnClassesThat().areAnnotatedWith(Deprecated.class)
.allowEmptyShould(true)
.as("No code should reference deprecated APIs")
.because("usage of deprecated APIs introduces risk that future upgrades and migrations will be blocked");

@Override
public Map<String, ArchRule> getRules() {
return Map.of("deprecated", noDeprecated);
}
}
```

When authoring rules about the usage of your own library code, it is recommended to colocate your rules library in the
same project as the library code. The ArchRules plugin will publish the rules in a separate Jar, and the Runner plugin
will select that jar for running rules, but these rule classes will not end up up in the runtime classpath.

## LICENSE

Copyright 2025 Netflix, Inc.

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
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

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

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.
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.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@ package com.netflix.nebula.archrules.gradle

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.kotlin.dsl.getByType

class ArchrulesLibraryPlugin : Plugin<Project> {
override fun apply(target: Project) {
override fun apply(project: Project) {
project.plugins.withId("java") {
val ext = project.extensions.getByType<JavaPluginExtension>()
val archRulesSourceSet = ext.sourceSets.create("archRules")
val version = ArchrulesLibraryPlugin::class.java.`package`.implementationVersion ?: "latest.release"
project.dependencies.add(archRulesSourceSet.implementationConfigurationName,
"com.netflix.nebula:nebula-archrules-core:$version"
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.netflix.nebula.archrules.gradle

import nebula.test.dsl.TestKitAssertions.assertThat
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.Test

class ArchrulesLibraryPluginTest {

@Test
fun `plugin registers library dependency`() {
val project = ProjectBuilder.builder().build()
project.plugins.apply("java")
project.plugins.apply(ArchrulesLibraryPlugin::class.java)
val configuration = project.configurations.findByName("archRulesImplementation")
assertThat(configuration).isNotNull
val coreLibrary = configuration!!.dependencies
.firstOrNull { it.group == "com.netflix.nebula" && it.name == "nebula-archrules-core" }
assertThat(coreLibrary).isNotNull
assertThat(coreLibrary!!.version).isEqualTo("latest.release")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ internal class IntegrationTest {
id("java-library")
id("com.netflix.nebula.archrules.library")
}
repositories {
maven("https://netflixoss.jfrog.io/artifactory/gradle-plugins")
mavenCentral()
}
src {
main {
java(
Expand All @@ -34,6 +38,40 @@ public class LibraryClass {
"""
)
}
sourceSet("archRules") {
java(
"com/example/library/LibraryArchRules.java",
//language=java
"""
package com.example.library;

import com.netflix.nebula.archrules.core.ArchRulesService;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.Priority;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import java.util.Map;
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target;
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.targetOwner;
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith;

public class LibraryArchRules implements ArchRulesService {
private final ArchRule noDeprecated = ArchRuleDefinition.priority(Priority.LOW)
.noClasses()
.should().accessTargetWhere(targetOwner(annotatedWith(Deprecated.class)))
.orShould().accessTargetWhere(target(annotatedWith(Deprecated.class)))
.orShould().dependOnClassesThat().areAnnotatedWith(Deprecated.class)
.allowEmptyShould(true)
.as("No code should reference deprecated APIs")
.because("usage of deprecated APIs introduces risk that future upgrades and migrations will be blocked");

@Override
public Map<String, ArchRule> getRules() {
return Map.of("deprecated", noDeprecated);
}
}
"""
)
}
}
}
subProject("code-to-check") {
Expand All @@ -48,8 +86,11 @@ public class LibraryClass {
}
}

val result = runner.run("check")
val result = runner.run("check", "compileArchRulesJava")

assertThat(result.task(":library-with-rules:compileArchRulesJava"))
.`as`("compile task runs for the archRules source set")
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE)
assertThat(result.task(":library-with-rules:check"))
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE)
assertThat(result.task(":code-to-check:check"))
Expand Down