Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
import org.jspecify.annotations.NullMarked;

import java.io.File;
import java.util.List;

@NullMarked
public interface RunRulesParams extends WorkParameters {
ConfigurableFileCollection getClassesToCheck();

Property<File> getDataOutputFile();

MapProperty<String, List<ArchrulesPredicate>> getPredicatesByName();

MapProperty<String, Priority> getPriorityOverridesByName();

MapProperty<String, Priority> getPriorityOverridesByClass();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.netflix.nebula.archrules.core.ArchRulesService;
import com.netflix.nebula.archrules.core.Runner;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.importer.ClassFileImporterWithPackage;
import com.tngtech.archunit.lang.Priority;
import org.gradle.workers.WorkAction;
Expand All @@ -10,14 +12,12 @@
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.*;
import java.util.stream.Collectors;

import static com.netflix.nebula.archrules.core.NoClassesMatchedEvent.NO_MATCH_MESSAGE;
import static com.tngtech.archunit.base.DescribedPredicate.not;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName;

@NullMarked
public abstract class RunRulesWorkAction implements WorkAction<RunRulesParams> {
Expand Down Expand Up @@ -79,7 +79,20 @@ public void execute() {
if (getParameters().getExcludedRules().get().contains(id)) {
LOGGER.info("Rule {} has been excluded for this source set", id);
} else {
final var result = Runner.check(archRule, classesToCheck);
final var predicates = getParameters().getPredicatesByName().orElse(Map.of()).get().getOrDefault(id, List.of());

var classesToCheckForRule = classesToCheck;
for (var predicate : predicates) {
classesToCheckForRule = classesToCheckForRule
.that(convertPredicate(predicate));
}

// TODO Remove debug prints
System.out.println("RULE: " + id);
System.out.println("PREDICATES: " + predicates);
System.out.println("CLASSES: " + classesToCheckForRule);

final var result = Runner.check(archRule, classesToCheckForRule);

// check if there is priority override by class first
var priority = getPriorityOverride(ruleClassName, id).orElse(result.getPriority());
Expand All @@ -103,4 +116,23 @@ public void execute() {

ViolationsUtil.writeDetails(getParameters().getDataOutputFile().get(), violationList);
}

private DescribedPredicate<JavaClass> convertPredicate(ArchrulesPredicate predicate) {
class ConvertingVisitor implements ArchrulesPredicateVisitor<DescribedPredicate<JavaClass>> {

@Override
public DescribedPredicate<JavaClass> visitNot(ArchrulesPredicate.NotPredicate predicate) {
return not(predicate.getPredicate().accept(this));
}

@Override
public DescribedPredicate<JavaClass> visitSimpleName(ArchrulesPredicate.SimpleNamePredicate predicate) {
return simpleName(predicate.getName());
}

}

return predicate.accept( new ConvertingVisitor());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.netflix.nebula.archrules.gradle

import java.io.Serializable

fun not(predicate: ArchrulesPredicate): ArchrulesPredicate =
ArchrulesPredicate.NotPredicate(predicate)

fun simpleName(name: String): ArchrulesPredicate =
ArchrulesPredicate.SimpleNamePredicate(name)

sealed class ArchrulesPredicate : Serializable {

abstract fun <R> accept(visitor: ArchrulesPredicateVisitor<R>): R

class NotPredicate(val predicate: ArchrulesPredicate) : ArchrulesPredicate() {
override fun <R> accept(visitor: ArchrulesPredicateVisitor<R>): R = visitor.visitNot(this)
}

class SimpleNamePredicate(val name: String) : ArchrulesPredicate() {
override fun <R> accept(visitor: ArchrulesPredicateVisitor<R>): R = visitor.visitSimpleName(this)
}

}

interface ArchrulesPredicateVisitor<R> {

fun visitNot(predicate: ArchrulesPredicate.NotPredicate): R

fun visitSimpleName(predicate: ArchrulesPredicate.SimpleNamePredicate): R

}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ class ArchrulesRunnerPlugin : Plugin<Project> {
tasks.register<CheckRulesTask>("checkArchRules" + sourceSet.name.capitalized()) {
description = "Checks ArchRules on ${sourceSet.name}"
rulesClasspath.setFrom(sourceSetArchRulesRuntime)
predicatesByName.set(
ext.ruleOverrides.map {
it.mapValues { it.value.predicates }
}
)
priorityOverridesByName.set(
ext.ruleOverrides.map {
it.mapValues { it.value.priority }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ abstract class CheckRulesTask @Inject constructor(private val workerExecutor: Wo
@get:OutputFile
abstract val dataFile: Property<File>

@get:Input
abstract val predicatesByName: MapProperty<String, List<ArchrulesPredicate>>

@get:Input
abstract val priorityOverridesByName: MapProperty<String, Priority>

Expand All @@ -47,6 +50,7 @@ abstract class CheckRulesTask @Inject constructor(private val workerExecutor: Wo
workQueue.submit(RunRulesWorkAction::class) {
getClassesToCheck().from(sourcesToCheck)
getDataOutputFile().set(dataFile)
getPredicatesByName().set(this@CheckRulesTask.predicatesByName)
getPriorityOverridesByName().set(this@CheckRulesTask.priorityOverridesByName)
getPriorityOverridesByClass().set(this@CheckRulesTask.priorityOverridesByClass)
getExcludedRules().set(this@CheckRulesTask.excludedRules)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ class RuleConfig {
var priority: Priority? = null
val sourceSetsToSkip: MutableList<String> = mutableListOf()

val predicates: MutableList<ArchrulesPredicate> = mutableListOf()

fun classesThat(predicate: ArchrulesPredicate) {
predicates += predicate
}

fun priority(priority: String) {
val validStrings = EnumSet.allOf(Priority::class.java).map { it.asString() }.toSet()
if (validStrings.contains(priority)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import nebula.test.dsl.subProject
import nebula.test.dsl.test
import nebula.test.dsl.testProject
import nebula.test.dsl.withGradle
import org.assertj.core.api.Assertions.from
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.named
import org.gradle.testfixtures.ProjectBuilder
Expand Down Expand Up @@ -643,6 +644,40 @@ archRules {
assertThat(deprecatedResults).isEmpty()
}

@Test
fun `rule level fine-grained filtering`() {
val runner = testProject(projectDir) {
setupConsumerProject {
rawBuildScript(
"""
archRules {
ruleName("deprecated") {
classesThat(com.netflix.nebula.archrules.gradle.not(com.netflix.nebula.archrules.gradle.simpleName("FailingCode")))
}
}
"""
)
}
}

val result = runner.run("checkArchRulesMain", "--stacktrace", "--info")

assertThat(result.task(":checkArchRulesMain"))
.`as`("archRules run for main source set")
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)

val mainReport = projectDir.resolve("build/reports/archrules/main.data")
val results = readDetails(mainReport)

val deprecatedForRemovalResults = results.filter { it.rule.ruleName == "deprecatedForRemoval" }
assertThat(deprecatedForRemovalResults).hasSize(1)

val deprecatedResults = results.filter { it.rule.ruleName == "deprecated" }
assertThat(deprecatedResults)
.singleElement()
.returns(RuleResultStatus.PASS, from(RuleResult::status))
}

@Test
fun `ruleClass level source set excludes`() {
val runner = testProject(projectDir) {
Expand Down