Skip to content

Commit 58a49a4

Browse files
committed
allow certain rules to be skipped for certain source sets
1 parent 85b01a5 commit 58a49a4

File tree

9 files changed

+248
-61
lines changed

9 files changed

+248
-61
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,19 @@ archRules {
180180

181181
The `ruleName` will take precedent over the `ruleClass`.
182182

183+
#### Disabling specific rules
184+
You can skip evaluating certain rules on specific source sets:
185+
```kotlin
186+
archRules {
187+
ruleClass("com.netflix.nebula.archrules.deprecation") {
188+
skipSourceSet("test")
189+
}
190+
ruleName("deprecated") {
191+
skipSourceSet("test")
192+
}
193+
}
194+
```
195+
183196
#### Configuring which code is tested
184197

185198
You can skip running rules on a specific source set:

nebula-archrules-gradle-plugin/src/main/java/com/netflix/nebula/archrules/gradle/RunRulesParams.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@
22

33
import com.tngtech.archunit.lang.Priority;
44
import org.gradle.api.file.ConfigurableFileCollection;
5+
import org.gradle.api.provider.ListProperty;
56
import org.gradle.api.provider.MapProperty;
67
import org.gradle.api.provider.Property;
78
import org.gradle.workers.WorkParameters;
8-
import org.jspecify.annotations.NonNull;
9+
import org.jspecify.annotations.NullMarked;
910

1011
import java.io.File;
1112

13+
@NullMarked
1214
public interface RunRulesParams extends WorkParameters {
1315
ConfigurableFileCollection getClassesToCheck();
1416

15-
Property<@NonNull File> getDataOutputFile();
17+
Property<File> getDataOutputFile();
1618

17-
MapProperty<@NonNull String, @NonNull Priority> getPriorityOverridesByName();
19+
MapProperty<String, Priority> getPriorityOverridesByName();
1820

19-
MapProperty<@NonNull String, @NonNull Priority> getPriorityOverridesByClass();
21+
MapProperty<String, Priority> getPriorityOverridesByClass();
22+
23+
ListProperty<String> getExcludedRules();
24+
25+
ListProperty<String> getExcludedRuleClasses();
2026
}

nebula-archrules-gradle-plugin/src/main/java/com/netflix/nebula/archrules/gradle/RunRulesWorkAction.java

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.tngtech.archunit.core.importer.ClassFileImporter;
66
import com.tngtech.archunit.lang.Priority;
77
import org.gradle.workers.WorkAction;
8+
import org.jspecify.annotations.NullMarked;
89
import org.slf4j.Logger;
910
import org.slf4j.LoggerFactory;
1011

@@ -15,14 +16,51 @@
1516
import java.util.ArrayList;
1617
import java.util.List;
1718
import java.util.Map;
19+
import java.util.Optional;
1820
import java.util.ServiceLoader;
1921
import java.util.stream.Collectors;
2022

2123
import static com.netflix.nebula.archrules.core.NoClassesMatchedEvent.NO_MATCH_MESSAGE;
2224

25+
@NullMarked
2326
public abstract class RunRulesWorkAction implements WorkAction<RunRulesParams> {
2427
private static final Logger LOGGER = LoggerFactory.getLogger(RunRulesWorkAction.class);
2528

29+
boolean isRuleClassExcluded(String ruleClassName) {
30+
for (String exclusion : getParameters().getExcludedRuleClasses().get()) {
31+
if (ruleClassName.startsWith(exclusion)) {
32+
return true;
33+
}
34+
}
35+
return false;
36+
}
37+
38+
Optional<Priority> getPriorityOverride(String ruleClassName, String ruleId) {
39+
Priority priority = null;
40+
41+
// check for exact rule match first
42+
final var overridesByName = getParameters().getPriorityOverridesByName().getOrElse(Map.of());
43+
if (overridesByName.containsKey(ruleId)) {
44+
priority = overridesByName.get(ruleId);
45+
}
46+
47+
if (priority == null) { // if no exact rule match, maybe there is a class-level match
48+
String classNameMatch = "";
49+
final var overridesByClass = getParameters().getPriorityOverridesByClass().getOrElse(Map.of());
50+
for (Map.Entry<String, Priority> override : overridesByClass.entrySet()) {
51+
String overrideRuleName = override.getKey();
52+
if (ruleClassName.startsWith(overrideRuleName)) {
53+
if (overrideRuleName.length() > classNameMatch.length()) { // prefer more specific
54+
priority = override.getValue();
55+
classNameMatch = overrideRuleName;
56+
}
57+
}
58+
}
59+
}
60+
61+
return Optional.ofNullable(priority);
62+
}
63+
2664
@Override
2765
public void execute() {
2866
ServiceLoader<ArchRulesService> ruleClasses = ServiceLoader.load(ArchRulesService.class);
@@ -34,40 +72,37 @@ public void execute() {
3472
final var classesToCheck = new ClassFileImporter()
3573
.importPaths(getParameters().getClassesToCheck().getFiles().stream().map(File::toPath).toList());
3674
final List<RuleResult> violationList = new ArrayList<>();
37-
final var overridesByName = getParameters().getPriorityOverridesByName().getOrElse(Map.of());
38-
final var overridesByClass = getParameters().getPriorityOverridesByClass().getOrElse(Map.of());
39-
40-
ruleClasses.forEach(ruleClass -> ruleClass.getRules().forEach((id, archRule) -> {
41-
final var result = Runner.check(archRule, classesToCheck);
4275

43-
// check if there is priority override by class first
44-
var priority = result.getPriority();
76+
ruleClasses.forEach(ruleClass -> {
4577
String ruleClassName = ruleClass.getClass().getCanonicalName();
46-
for (Map.Entry<String, Priority> override : overridesByClass.entrySet()) {
47-
String overrideRuleName = override.getKey();
48-
if (ruleClassName.startsWith(overrideRuleName)) {
49-
priority = override.getValue();
50-
break;
51-
}
52-
}
53-
// then check if there is a priority override by name
54-
if (overridesByName.containsKey(id)) {
55-
priority = overridesByName.get(id);
56-
}
57-
58-
final var rule = new Rule(ruleClassName, id, archRule.getDescription(), priority);
59-
if (result.hasViolation()) {
60-
result.getFailureReport().getDetails().forEach(detail -> {
61-
if (detail.equals(NO_MATCH_MESSAGE)) {
62-
violationList.add(new RuleResult(rule, detail, RuleResultStatus.NO_MATCH));
78+
if (isRuleClassExcluded(ruleClassName)) {
79+
LOGGER.info("Rule class {} has been excluded for this source set", ruleClass.getClass().getName());
80+
} else {
81+
ruleClass.getRules().forEach((id, archRule) -> {
82+
if (getParameters().getExcludedRules().get().contains(id)) {
83+
LOGGER.info("Rule {} has been excluded for this source set", id);
6384
} else {
64-
violationList.add(new RuleResult(rule, detail, RuleResultStatus.FAIL));
85+
final var result = Runner.check(archRule, classesToCheck);
86+
87+
// check if there is priority override by class first
88+
var priority = getPriorityOverride(ruleClassName, id).orElse(result.getPriority());
89+
90+
final var rule = new Rule(ruleClassName, id, archRule.getDescription(), priority);
91+
if (result.hasViolation()) {
92+
result.getFailureReport().getDetails().forEach(detail -> {
93+
if (detail.equals(NO_MATCH_MESSAGE)) {
94+
violationList.add(new RuleResult(rule, detail, RuleResultStatus.NO_MATCH));
95+
} else {
96+
violationList.add(new RuleResult(rule, detail, RuleResultStatus.FAIL));
97+
}
98+
});
99+
} else {
100+
violationList.add(new RuleResult(rule, "", RuleResultStatus.PASS));
101+
}
65102
}
66103
});
67-
} else {
68-
violationList.add(new RuleResult(rule, "", RuleResultStatus.PASS));
69104
}
70-
}));
105+
});
71106

72107
try (var out = new ObjectOutputStream(new FileOutputStream(getParameters().getDataOutputFile().get()))) {
73108
out.writeInt(violationList.size());

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

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,8 @@ abstract class ArchrulesExtension {
2121
abstract val failureThreshold: Property<Priority>
2222
abstract val consoleDetailsThreshold: Property<Priority>
2323

24-
/**
25-
* Allow priority overrides
26-
*/
27-
abstract val priorityOverridesByRuleName: MapProperty<String, Priority>
28-
abstract val priorityOverridesByRuleClass: MapProperty<String, Priority>
24+
abstract val ruleOverrides: MapProperty<String, RuleConfig>
25+
abstract val ruleClassOverrides: MapProperty<String, RuleConfig>
2926

3027
/**
3128
* Add a source set to the list of sourcesets to skip
@@ -52,29 +49,14 @@ abstract class ArchrulesExtension {
5249

5350
@Deprecated("use ruleName instead")
5451
fun rule(ruleName: String, action: Action<RuleConfig>) {
55-
val config = RuleConfig()
56-
action.execute(config)
57-
58-
config.priority?.let { priority ->
59-
priorityOverridesByRuleName.put(ruleName, priority)
60-
}
52+
ruleOverrides.put(ruleName, RuleConfig().apply { action.execute(this) })
6153
}
6254

6355
fun ruleName(ruleName: String, action: Action<RuleConfig>) {
64-
val config = RuleConfig()
65-
action.execute(config)
66-
67-
config.priority?.let { priority ->
68-
priorityOverridesByRuleName.put(ruleName, priority)
69-
}
56+
ruleOverrides.put(ruleName, RuleConfig().apply { action.execute(this) })
7057
}
7158

7259
fun ruleClass(ruleClass: String, action: Action<RuleConfig>) {
73-
val config = RuleConfig()
74-
action.execute(config)
75-
76-
config.priority?.let { priority ->
77-
priorityOverridesByRuleClass.put(ruleClass, priority)
78-
}
60+
ruleClassOverrides.put(ruleClass, RuleConfig().apply { action.execute(this) })
7961
}
8062
}

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import org.gradle.api.attributes.Usage
1010
import org.gradle.api.plugins.JavaPluginExtension
1111
import org.gradle.api.tasks.SourceSet
1212
import org.gradle.internal.extensions.stdlib.capitalized
13-
import org.gradle.kotlin.dsl.*
13+
import org.gradle.kotlin.dsl.add
14+
import org.gradle.kotlin.dsl.create
15+
import org.gradle.kotlin.dsl.getByType
16+
import org.gradle.kotlin.dsl.named
17+
import org.gradle.kotlin.dsl.register
18+
import org.gradle.kotlin.dsl.withType
1419

1520
class ArchrulesRunnerPlugin : Plugin<Project> {
1621
override fun apply(project: Project) {
@@ -87,11 +92,34 @@ class ArchrulesRunnerPlugin : Plugin<Project> {
8792
attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(ARCH_RULES))
8893
}
8994
}
95+
9096
tasks.register<CheckRulesTask>("checkArchRules" + sourceSet.name.capitalized()) {
9197
description = "Checks ArchRules on ${sourceSet.name}"
9298
rulesClasspath.setFrom(sourceSetArchRulesRuntime)
93-
priorityOverridesByName.set(ext.priorityOverridesByRuleName)
94-
priorityOverridesByClass.set(ext.priorityOverridesByRuleClass)
99+
priorityOverridesByName.set(
100+
ext.ruleOverrides.map {
101+
it.mapValues { it.value.priority }
102+
.filterValues { it != null }
103+
.mapValues { it.value!! } // could be improved by https://youtrack.jetbrains.com/issue/KT-4734
104+
}
105+
)
106+
priorityOverridesByClass.set(
107+
ext.ruleClassOverrides.map {
108+
it.mapValues { it.value.priority }
109+
.filterValues { it != null }
110+
.mapValues { it.value!! } // could be improved by https://youtrack.jetbrains.com/issue/KT-4734
111+
}
112+
)
113+
excludedRules.set(
114+
ext.ruleOverrides.map {
115+
it.filter { it.value.sourceSetsToSkip.contains(sourceSet.name) }.map { it.key }
116+
}
117+
)
118+
excludedRuleClasses.set(
119+
ext.ruleClassOverrides.map {
120+
it.filter { it.value.sourceSetsToSkip.contains(sourceSet.name) }.map { it.key }
121+
}
122+
)
95123
dataFile.set(archRulesReportDir.map {
96124
it.file(sourceSet.name + ".data").asFile
97125
})

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.netflix.nebula.archrules.gradle;
33
import com.tngtech.archunit.lang.Priority
44
import org.gradle.api.DefaultTask
55
import org.gradle.api.file.ConfigurableFileCollection
6+
import org.gradle.api.provider.ListProperty
67
import org.gradle.api.provider.MapProperty
78
import org.gradle.api.provider.Property
89
import org.gradle.api.tasks.*
@@ -32,6 +33,12 @@ abstract class CheckRulesTask @Inject constructor(private val workerExecutor: Wo
3233
@get:Input
3334
abstract val priorityOverridesByClass: MapProperty<String, Priority>
3435

36+
@get:Input
37+
abstract val excludedRules: ListProperty<String>
38+
39+
@get:Input
40+
abstract val excludedRuleClasses: ListProperty<String>
41+
3542
@TaskAction
3643
fun checkRules() {
3744
val workQueue: WorkQueue = workerExecutor.classLoaderIsolation {
@@ -42,6 +49,8 @@ abstract class CheckRulesTask @Inject constructor(private val workerExecutor: Wo
4249
getDataOutputFile().set(dataFile)
4350
getPriorityOverridesByName().set(this@CheckRulesTask.priorityOverridesByName)
4451
getPriorityOverridesByClass().set(this@CheckRulesTask.priorityOverridesByClass)
52+
getExcludedRules().set(this@CheckRulesTask.excludedRules)
53+
getExcludedRuleClasses().set(this@CheckRulesTask.excludedRuleClasses)
4554
}
4655
}
4756
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class RuleConfig {
1212
}
1313

1414
var priority: Priority? = null
15+
val sourceSetsToSkip: MutableList<String> = mutableListOf()
1516

1617
fun priority(priority: String) {
1718
val validStrings = EnumSet.allOf(Priority::class.java).map { it.asString() }.toSet()
@@ -20,12 +21,16 @@ class RuleConfig {
2021
} else {
2122
LOGGER.warn(
2223
"Invalid ArchRule priority '$priority'. " +
23-
"Must be one of the following (case-sensitive): ${validStrings.joinToString(", ")}"
24+
"Must be one of the following (case-sensitive): ${validStrings.joinToString(", ")}"
2425
)
2526
}
2627
}
2728

2829
fun priority(priority: Priority) {
2930
this.priority = priority
3031
}
32+
33+
fun skipSourceSet(name: String) {
34+
sourceSetsToSkip.add(name)
35+
}
3136
}

0 commit comments

Comments
 (0)