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
23 changes: 23 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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
4 changes: 1 addition & 3 deletions archrules-deprecation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ plugins {
id("com.netflix.nebula.archrules.library")
}
description = "Arch Rules for detecting usage of deprecated code"
repositories {
mavenCentral()
}

dependencies {
archRulesImplementation(libs.jspecify)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class DeprecationRule implements ArchRulesService {
* - Kotlin @Deprecated annotations
* - Kotlin @DeprecatedSinceKotlin annotations
*/
public static ArchRule deprecationRule = ArchRuleDefinition.priority(Priority.LOW)
public static final ArchRule deprecationRule = ArchRuleDefinition.priority(Priority.LOW)
.noClasses()
// Java deprecated
.should().dependOnClassesThat().areAnnotatedWith(Deprecated.class)
Expand Down Expand Up @@ -58,7 +58,7 @@ public boolean test(JavaAnnotation<?> annotation) {
}
};

public static ArchRule deprecationForRemovalRule = ArchRuleDefinition.priority(Priority.MEDIUM)
public static final ArchRule deprecationForRemovalRule = ArchRuleDefinition.priority(Priority.MEDIUM)
.noClasses()
.should().dependOnClassesThat().areAnnotatedWith(deprecatedForRemoval)
.orShould().accessTargetWhere(targetOwner(annotatedWith(deprecatedForRemoval)))
Expand Down
23 changes: 23 additions & 0 deletions archrules-gradle-plugin-development/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
plugins {
id("com.netflix.nebula.library")
id("com.netflix.nebula.archrules.library")
}

description = "Arch Rules for detecting bad practices when developing Gradle plugins"

dependencies {
archRulesImplementation(libs.jspecify)

archRulesTestImplementation(libs.assertj)
archRulesTestImplementation(libs.logback)
archRulesTestImplementation(gradleApi())
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
}

dependencyLocking {
lockAllConfigurations()
}
27 changes: 27 additions & 0 deletions archrules-gradle-plugin-development/gradle.lockfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
ch.qos.logback:logback-classic:1.5.20=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
ch.qos.logback:logback-core:1.5.20=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
com.netflix.nebula:archrules-deprecation:0.4.0=archRules
com.netflix.nebula:archrules-joda:0.4.0=archRules
com.netflix.nebula:archrules-nullability:0.4.0=archRules
com.netflix.nebula:archrules-testing-frameworks:0.4.0=archRules
com.netflix.nebula:nebula-archrules-core:0.3.0=archRules
com.netflix.nebula:nebula-archrules-core:0.5.2=archRulesArchRulesRuntime,archRulesCompileClasspath,archRulesRuntimeClasspath,archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
com.tngtech.archunit:archunit:1.4.1=archRules,archRulesArchRulesRuntime,archRulesCompileClasspath,archRulesRuntimeClasspath,archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
net.bytebuddy:byte-buddy:1.17.7=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=archRulesTestCompileClasspath
org.assertj:assertj-core:3.27.6=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
org.jspecify:jspecify:1.0.0=archRules,archRulesArchRulesRuntime,archRulesCompileClasspath,archRulesRuntimeClasspath,archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.12.2=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.12.2=archRulesTestArchRulesRuntime,archRulesTestRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.12.2=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
org.junit.jupiter:junit-jupiter:5.12.2=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
org.junit.platform:junit-platform-commons:1.12.2=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
org.junit.platform:junit-platform-engine:1.12.2=archRulesTestArchRulesRuntime,archRulesTestRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.12.2=archRulesTestArchRulesRuntime,archRulesTestRuntimeClasspath
org.junit:junit-bom:5.12.2=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
org.slf4j:slf4j-api:2.0.17=archRules,archRulesArchRulesRuntime,archRulesCompileClasspath,archRulesRuntimeClasspath,archRulesTestArchRulesRuntime,archRulesTestCompileClasspath,archRulesTestRuntimeClasspath
empty=annotationProcessor,archRulesAnnotationProcessor,archRulesTestAnnotationProcessor,compileClasspath,mainArchRulesRuntime,runtimeClasspath,testAnnotationProcessor,testArchRulesRuntime,testCompileClasspath,testRuntimeClasspath
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.netflix.nebula.archrules.gradleplugins;

import com.netflix.nebula.archrules.core.ArchRulesService;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaAccess;
import com.tngtech.archunit.core.domain.JavaFieldAccess;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.Priority;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import org.jspecify.annotations.NullMarked;

import java.util.HashMap;
import java.util.Map;

/**
* Rules for Gradle task action methods to ensure Gradle 10 compatibility.
*/
@NullMarked
public class GradleTaskActionRule implements ArchRulesService {

/**
* Prevents {@code @TaskAction} methods from accessing {@code Project}.
* <p>
* Accessing {@code Project} in task actions breaks configuration cache and will
* cause runtime errors in Gradle 10+. Move Project access to configuration time
* (constructor/initializer) and use task properties instead.
*/
public static final ArchRule taskActionShouldNotAccessProject = ArchRuleDefinition.priority(Priority.MEDIUM)
.methods()
.that(areAnnotatedWithTaskAction())
.should(notAccessProject())
.allowEmptyShould(true)
.because(
"Accessing Project in @TaskAction methods breaks configuration cache and will be removed in Gradle 10. " +
"Move Project access to task configuration time and use task inputs/properties instead. " +
"See https://docs.gradle.org/9.2.0/userguide/upgrading_version_7.html#task_project"
);

/**
* Prevents {@code @TaskAction} methods from calling {@code getTaskDependencies()}.
* <p>
* Calling {@code getTaskDependencies()} in task actions breaks configuration cache and will
* cause runtime errors in Gradle 10+. Task dependencies should be declared at configuration time.
*/
public static final ArchRule taskActionShouldNotCallGetTaskDependencies = ArchRuleDefinition.priority(Priority.MEDIUM)
.methods()
.that(areAnnotatedWithTaskAction())
.should(notCallGetTaskDependencies())
.allowEmptyShould(true)
.because(
"Calling getTaskDependencies() in @TaskAction methods breaks configuration cache and will be removed in Gradle 10. " +
"Declare task dependencies at configuration time instead. " +
"See https://docs.gradle.org/9.2.0/userguide/upgrading_version_7.html#task_dependencies"
);

private static DescribedPredicate<JavaMethod> areAnnotatedWithTaskAction() {
return new DescribedPredicate<JavaMethod>("are annotated with @TaskAction") {
@Override
public boolean test(JavaMethod method) {
return method.isAnnotatedWith("org.gradle.api.tasks.TaskAction");
}
};
}

private static ArchCondition<JavaMethod> notAccessProject() {
return notAccessType("Project", "org.gradle.api.Project", "getProject");
}

private static ArchCondition<JavaMethod> notCallGetTaskDependencies() {
return notAccessType("TaskDependency", "org.gradle.api.tasks.TaskDependency", "getTaskDependencies");
}

private static ArchCondition<JavaMethod> notAccessType(String displayName, String fullyQualifiedClassName, String getterMethodName) {
return new ArchCondition<JavaMethod>("not access " + displayName) {
@Override
public void check(JavaMethod method, ConditionEvents events) {
for (JavaAccess<?> access : method.getAccessesFromSelf()) {
if (isTargetTypeAccess(access) || isGetterCall(access) || isTargetTypeFieldAccess(access)) {
String message = String.format(
"Method %s.%s() accesses %s at %s",
method.getOwner().getSimpleName(),
method.getName(),
displayName,
access.getDescription()
);
events.add(SimpleConditionEvent.violated(access, message));
}
}
}

private boolean isTargetTypeAccess(JavaAccess<?> access) {
return fullyQualifiedClassName.equals(access.getTargetOwner().getFullName());
}

private boolean isGetterCall(JavaAccess<?> access) {
if (!getterMethodName.equals(access.getName())) {
return false;
}
return access.getTargetOwner().isAssignableTo("org.gradle.api.Task");
}

private boolean isTargetTypeFieldAccess(JavaAccess<?> access) {
if (access instanceof JavaFieldAccess) {
JavaFieldAccess fieldAccess = (JavaFieldAccess) access;
return fieldAccess.getTarget().getRawType().isAssignableTo(fullyQualifiedClassName);
}
return false;
}
};
}

@Override
public Map<String, ArchRule> getRules() {
Map<String, ArchRule> rules = new HashMap<>();
rules.put("gradle-task-action-project-access", taskActionShouldNotAccessProject);
rules.put("gradle-task-action-task-dependencies", taskActionShouldNotCallGetTaskDependencies);
return rules;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.netflix.nebula.archrules.gradleplugins;

import com.netflix.nebula.archrules.core.Runner;
import com.tngtech.archunit.lang.EvaluationResult;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.assertj.core.api.Assertions.assertThat;

public class GradleTaskActionRuleTest {
private static final Logger LOG = LoggerFactory.getLogger(GradleTaskActionRuleTest.class);

@Test
public void taskActionAccessingProject_should_fail() {
final EvaluationResult result = Runner.check(
GradleTaskActionRule.taskActionShouldNotAccessProject,
TaskAccessingProject.class
);
LOG.info(result.getFailureReport().toString());
assertThat(result.hasViolation()).isTrue();
}

@Test
public void taskActionCallingGetProject_should_fail() {
final EvaluationResult result = Runner.check(
GradleTaskActionRule.taskActionShouldNotAccessProject,
TaskCallingGetProject.class
);
LOG.info(result.getFailureReport().toString());
assertThat(result.hasViolation()).isTrue();
}

@Test
public void taskActionNotAccessingProject_should_pass() {
final EvaluationResult result = Runner.check(
GradleTaskActionRule.taskActionShouldNotAccessProject,
TaskNotAccessingProject.class
);
LOG.info(result.getFailureReport().toString());
assertThat(result.hasViolation()).isFalse();
}

@Test
public void taskActionAccessingProjectInConstructor_should_pass() {
final EvaluationResult result = Runner.check(
GradleTaskActionRule.taskActionShouldNotAccessProject,
TaskAccessingProjectInConstructor.class
);
LOG.info(result.getFailureReport().toString());
assertThat(result.hasViolation()).isFalse();
}

@Test
public void taskActionCallingGetTaskDependencies_should_fail() {
final EvaluationResult result = Runner.check(
GradleTaskActionRule.taskActionShouldNotCallGetTaskDependencies,
TaskCallingGetTaskDependencies.class
);
LOG.info(result.getFailureReport().toString());
assertThat(result.hasViolation()).isTrue();
}

@Test
public void taskActionNotCallingGetTaskDependencies_should_pass() {
final EvaluationResult result = Runner.check(
GradleTaskActionRule.taskActionShouldNotCallGetTaskDependencies,
TaskNotCallingGetTaskDependencies.class
);
LOG.info(result.getFailureReport().toString());
assertThat(result.hasViolation()).isFalse();
}

@SuppressWarnings("unused")
public static abstract class TaskAccessingProject extends DefaultTask {
@TaskAction
public void execute() {
Project project = getProject();
String version = project.getVersion().toString();
System.out.println("Version: " + version);
}
}

@SuppressWarnings("unused")
public static abstract class TaskCallingGetProject extends DefaultTask {
@TaskAction
public void execute() {
getProject();
System.out.println("Task executed");
}
}

@SuppressWarnings("unused")
public static abstract class TaskNotAccessingProject extends DefaultTask {
@Input
public abstract Property<String> getVersion();

@TaskAction
public void execute() {
String version = getVersion().get();
System.out.println("Version: " + version);
}
}

@SuppressWarnings("unused")
public static abstract class TaskAccessingProjectInConstructor extends DefaultTask {
@Input
public abstract Property<String> getVersion();

public TaskAccessingProjectInConstructor() {
getVersion().set(getProject().getVersion().toString());
}

@TaskAction
public void execute() {
String version = getVersion().get();
System.out.println("Version: " + version);
}
}

@SuppressWarnings("unused")
public static abstract class TaskCallingGetTaskDependencies extends DefaultTask {
@TaskAction
public void execute() {
getTaskDependencies();
System.out.println("Task executed");
}
}

@SuppressWarnings("unused")
public static abstract class TaskNotCallingGetTaskDependencies extends DefaultTask {
@TaskAction
public void execute() {
System.out.println("Task executed");
}
}
}
5 changes: 2 additions & 3 deletions archrules-joda/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ plugins {
id("com.netflix.nebula.library")
id("com.netflix.nebula.archrules.library")
}

description = "Arch Rules for detecting usage of Joda"
repositories {
mavenCentral()
}

dependencies {
archRulesImplementation(libs.jspecify)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class JodaRule implements ArchRulesService {
/**
* This rule is a stop-gap to find all usages of Joda.
*/
public static ArchRule jodaRule = ArchRuleDefinition.priority(Priority.MEDIUM)
public static final ArchRule jodaRule = ArchRuleDefinition.priority(Priority.MEDIUM)
.noClasses()
.should(GeneralCodingRules.USE_JODATIME)
.allowEmptyShould(true)
Expand Down
Loading