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
24 changes: 24 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
root = true

[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
ij_continuation_indent_size = 8
tab_width = 4
trim_trailing_whitespace = true

[{*.kt,*.kts}]
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_name_count_to_use_star_import_for_members = 20
ij_kotlin_name_count_to_use_star_import = 20
ij_kotlin_continuation_indent_size = 4
ktlint_code_style=intellij_idea

[*.java]
ij_java_names_count_to_use_import_on_demand = 20
ij_java_class_count_to_use_import_on_demand = 20
ij_java_packages_to_use_import_on_demand = 20
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,19 @@ public class LibraryArchRulesTest {
public void aMethod() {
LibraryClass.newApi();
}
}
}
static class FailingCode {
public void aMethod() {
LibraryClass.deprecatedApi();
}
}

@Test
public void test_pass() {
EvaluationResult result = Runner.check(LibraryArchRules.noDeprecated, PassingCode.class);
Assertions.assertFalse(result.hasViolation());
}

@Test
public void test_fail() {
EvaluationResult result = Runner.check(LibraryArchRules.noDeprecated, FailingCode.class);
Expand Down Expand Up @@ -140,7 +140,7 @@ dependencies {
}
```

#### Report Configuration
#### Report Configuration

The plugin can generate JSON and console reports. Both are enabled by default. The console report can be disabled:
```kotlin
Expand All @@ -164,6 +164,17 @@ archRules {
```
The default threshold is MEDIUM.

#### Overriding rule priority

You can override the default priority of a rule using the `ruleName` and priority as `LOW`, `MEDIUM`, or `HIGH`:
```kotlin
archRules {
rule("deprecated") {
priority("HIGH")
}
}
```

#### Configuring which code is tested

You can skip running rules on a specific source set:
Expand Down Expand Up @@ -200,4 +211,4 @@ 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.
language governing permissions and limitations under the License.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.netflix.nebula.archrules.gradle;

import com.tngtech.archunit.lang.Priority;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.workers.WorkParameters;
import org.jspecify.annotations.NonNull;
Expand All @@ -11,4 +13,6 @@ public interface RunRulesParams extends WorkParameters {
ConfigurableFileCollection getClassesToCheck();

Property<@NonNull File> getDataOutputFile();

MapProperty<@NonNull String, @NonNull Priority> getPriorityOverrides();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.netflix.nebula.archrules.core.ArchRulesService;
import com.netflix.nebula.archrules.core.Runner;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.Priority;
import org.gradle.workers.WorkAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -13,6 +14,7 @@
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Collectors;

Expand All @@ -32,10 +34,17 @@ public void execute() {
final var classesToCheck = new ClassFileImporter()
.importPaths(getParameters().getClassesToCheck().getFiles().stream().map(File::toPath).toList());
final List<RuleResult> violationList = new ArrayList<>();
final var overrides = getParameters().getPriorityOverrides().getOrElse(Map.of());
ruleClasses.forEach(ruleClass -> ruleClass.getRules().forEach((id, archRule) -> {
final var result = Runner.check(archRule, classesToCheck);
//TODO: allow priority overrides by rule name
final var rule = new Rule(ruleClass.getClass().getCanonicalName(), id, archRule.getDescription(), result.getPriority());

// check if there is a priority override
var priority = result.getPriority();
if (overrides.containsKey(id)) {
priority = overrides.get(id);
}

final var rule = new Rule(ruleClass.getClass().getCanonicalName(), id, archRule.getDescription(), priority);
if (result.hasViolation()) {
result.getFailureReport().getDetails().forEach(detail -> {
if (detail.equals(NO_MATCH_MESSAGE)) {
Expand All @@ -62,4 +71,4 @@ public void execute() {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.netflix.nebula.archrules.gradle

import com.tngtech.archunit.lang.Priority
import org.gradle.api.Action
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property

abstract class ArchrulesExtension {
Expand All @@ -19,6 +21,11 @@ abstract class ArchrulesExtension {
abstract val failureThreshold: Property<Priority>
abstract val consoleDetailsThreshold: Property<Priority>

/**
* Allow priority overrides
*/
abstract val priorityOverrides: MapProperty<String, Priority>

/**
* Add a source set to the list of sourcesets to skip
*/
Expand All @@ -41,4 +48,13 @@ abstract class ArchrulesExtension {
fun consoleDetailsThreshold(priority: String) {
consoleDetailsThreshold.set(Priority.valueOf(priority))
}
}

fun rule(ruleName: String, action: Action<RuleConfig>) {
val config = RuleConfig()
action.execute(config)

config.priority?.let { priority ->
priorityOverrides.put(ruleName, priority)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class ArchrulesRunnerPlugin : Plugin<Project> {
tasks.register<CheckRulesTask>("checkArchRules" + sourceSet.name.capitalized()) {
description = "Checks ArchRules on ${sourceSet.name}"
rulesClasspath.setFrom(sourceSetArchRulesRuntime)
priorityOverrides.set(ext.priorityOverrides)
dataFile.set(archRulesReportDir.map {
it.file(sourceSet.name + ".data").asFile
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.netflix.nebula.archrules.gradle;

import com.tngtech.archunit.lang.Priority
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.submit
Expand All @@ -24,6 +26,9 @@ abstract class CheckRulesTask @Inject constructor(private val workerExecutor: Wo
@get:OutputFile
abstract val dataFile: Property<File>

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

@TaskAction
fun checkRules() {
val workQueue: WorkQueue = workerExecutor.classLoaderIsolation {
Expand All @@ -32,6 +37,7 @@ abstract class CheckRulesTask @Inject constructor(private val workerExecutor: Wo
workQueue.submit(RunRulesWorkAction::class) {
getClassesToCheck().from(sourcesToCheck)
getDataOutputFile().set(dataFile)
getPriorityOverrides().set(this@CheckRulesTask.priorityOverrides)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.netflix.nebula.archrules.gradle

import com.tngtech.archunit.lang.Priority
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.EnumSet

class RuleConfig {
companion object {
@JvmStatic
val LOGGER: Logger = LoggerFactory.getLogger(RuleConfig::class.java)
}

var priority: Priority? = null

fun priority(priority: String) {
val validStrings = EnumSet.allOf(Priority::class.java).map { it.asString() }.toSet()
if (validStrings.contains(priority)) {
priority(Priority.valueOf(priority))
} else {
LOGGER.warn(
"Invalid ArchRule priority '$priority'. " +
"Must be one of the following (case-sensitive): ${validStrings.joinToString(", ")}"
)
}
}

fun priority(priority: Priority) {
this.priority = priority
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ archRules {
"""
archRules {
skipPassingSummaries = true
}
}
"""
)
src {
Expand Down Expand Up @@ -341,7 +341,7 @@ archRules {
"""
archRules {
consoleDetailsThreshold("MEDIUM")
}
}
"""
)
}
Expand Down Expand Up @@ -370,7 +370,7 @@ archRules {
"""
archRules {
consoleDetailsThreshold("LOW")
}
}
"""
)
}
Expand Down Expand Up @@ -427,7 +427,7 @@ archRules {
"""
archRules {
skipSourceSet("test")
}
}
"""
)
}
Expand Down Expand Up @@ -464,4 +464,77 @@ archRules {
}
return list
}

@Test
fun `can override priority of a rule`() {
val runner = testProject(projectDir) {
setupConsumerProject {
rawBuildScript(
"""
archRules {
rule("deprecated") {
priority("HIGH")
}
}
"""
)
}
}

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

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)

// assert deprecated (LOW) is overridden
val deprecatedResults = results.filter { it.rule.ruleName.equals("deprecated") }
assertThat(deprecatedResults).hasSize(2)
deprecatedResults.forEach { result ->
assertThat(result.rule.priority).isEqualTo(Priority.HIGH)
}

// assert deprecatedForRemoval (MEDIUM), which is in the same class but not the same rule, is not overridden
val deprecatedForRemovalResult = results.firstOrNull { it.rule.ruleName.equals("deprecatedForRemoval") }
assertThat(deprecatedForRemovalResult).isNotNull
assertThat(deprecatedForRemovalResult!!.rule.priority).isEqualTo(Priority.MEDIUM)
}

@Test
fun `invalid priority string logs warning and does not override`() {
val runner = testProject(projectDir) {
setupConsumerProject {
rawBuildScript(
"""
archRules {
rule("deprecatedForRemoval") {
priority("NONE")
}
}
"""
)
}
}

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

assertThat(result.output)
.contains("Invalid ArchRule priority 'NONE'")
.contains("Must be one of the following (case-sensitive): HIGH, MEDIUM, LOW")

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)

// assert priority stays default
val deprecationResult = results.firstOrNull { it.rule.ruleName.equals("deprecated") }
assertThat(deprecationResult).isNotNull
assertThat(deprecationResult!!.rule.priority).isEqualTo(Priority.LOW)
}
}