From 8d3388054062b476f522e324f27b10bfc37feba8 Mon Sep 17 00:00:00 2001 From: Thomas Broyer Date: Wed, 17 Apr 2024 20:18:54 +0200 Subject: [PATCH] Reject incompatible slf4j-api versions from Slf4j implementations --- samples/sample-all/build.out | 2 +- .../detection/rules/CapabilityDefinition.java | 8 +- .../rules/CapabilityDefinitionRule.java | 6 +- .../Slf4jVersionCompatibilityRule.java | 85 +++++++++++++++++++ ...litiesPluginDetectionFunctionalTest.groovy | 16 ++-- 5 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/logging/Slf4jVersionCompatibilityRule.java diff --git a/samples/sample-all/build.out b/samples/sample-all/build.out index 7ddd939..0e24e06 100644 --- a/samples/sample-all/build.out +++ b/samples/sample-all/build.out @@ -23,7 +23,7 @@ compileClasspath - Compile classpath for source set 'main'. | \--- org.ow2.asm:asm:7.1 -> 9.2 +--- cglib:cglib:3.3.0 (*) +--- ch.qos.logback:logback-classic:1.5.0 -> org.slf4j:slf4j-simple:2.0.12 -| +--- org.slf4j:slf4j-api:2.0.12 (*) +| +--- org.slf4j:slf4j-api:{require 2.0.12; reject [,1.8.0-alpha0)} -> 2.0.12 (*) | \--- org.slf4j:slf4j-bom:2.0.12 (*) +--- com.github.spotbugs:spotbugs-annotations:4.8.3 | \--- com.google.code.findbugs:jsr305:3.0.2 diff --git a/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/CapabilityDefinition.java b/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/CapabilityDefinition.java index 83919ed..5a5bfca 100644 --- a/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/CapabilityDefinition.java +++ b/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/CapabilityDefinition.java @@ -46,6 +46,7 @@ import org.gradlex.jvm.dependency.conflict.detection.rules.jakarta.JavaxXmlBindApiRule; import org.gradlex.jvm.dependency.conflict.detection.rules.jakarta.JavaxXmlWsApiRule; import org.gradlex.jvm.dependency.conflict.detection.rules.logging.LoggingModuleIdentifiers; +import org.gradlex.jvm.dependency.conflict.detection.rules.logging.Slf4jVersionCompatibilityRule; import org.gradlex.jvm.dependency.conflict.resolution.DefaultResolutionStrategy; import java.util.Arrays; @@ -503,8 +504,13 @@ public enum CapabilityDefinition { * * `slf4j-jcl` to use Jakarta Commons Logging * * `slf4j-jdk14` to use Java Util Logging * * `log4j-slf4j-impl` to use Log4J2 + *

+ * Additionally, Slf4j 1 and 2 aren't compatible with each other, + * so the capability version depends on the implementation, and + * dependency on `slf4j-api` is adjusted to reject incompatible + * versions. */ - SLF4J_IMPL(NONE, FixedVersionCapabilityDefinitionRule.class, + SLF4J_IMPL(NONE, Slf4jVersionCompatibilityRule.class, LoggingModuleIdentifiers.SLF4J_SIMPLE.moduleId, LoggingModuleIdentifiers.LOGBACK_CLASSIC.moduleId, LoggingModuleIdentifiers.SLF4J_LOG4J12.moduleId, diff --git a/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/CapabilityDefinitionRule.java b/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/CapabilityDefinitionRule.java index 87c0dcc..81e86c2 100644 --- a/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/CapabilityDefinitionRule.java +++ b/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/CapabilityDefinitionRule.java @@ -41,7 +41,7 @@ public final void execute(ComponentMetadataContext context) { variant.withCapabilities(capabilities -> capabilities.addCapability( definition.getGroup(), definition.getCapabilityName(), getVersion(context.getDetails().getId()) )); - additionalAdjustments(variant); + additionalAdjustments(context.getDetails().getId(), variant); }); } } @@ -54,5 +54,9 @@ protected String getVersion(ModuleVersionIdentifier id) { return id.getVersion(); } + protected void additionalAdjustments(ModuleVersionIdentifier id, VariantMetadata variant) { + additionalAdjustments(variant); + } + protected void additionalAdjustments(VariantMetadata variant) { } } diff --git a/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/logging/Slf4jVersionCompatibilityRule.java b/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/logging/Slf4jVersionCompatibilityRule.java new file mode 100644 index 0000000..208c640 --- /dev/null +++ b/src/main/java/org/gradlex/jvm/dependency/conflict/detection/rules/logging/Slf4jVersionCompatibilityRule.java @@ -0,0 +1,85 @@ +/* + * Copyright the GradleX team. + * + * 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. + */ + +package org.gradlex.jvm.dependency.conflict.detection.rules.logging; + +import org.gradle.api.artifacts.CacheableRule; +import org.gradle.api.artifacts.DirectDependencyMetadata; +import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.gradle.api.artifacts.VariantMetadata; +import org.gradlex.jvm.dependency.conflict.detection.rules.CapabilityDefinition; +import org.gradlex.jvm.dependency.conflict.detection.rules.CapabilityDefinitionRule; + +import javax.inject.Inject; + +/** + * Slf4j 2 is a major breaking change from Slf4j 1. + * This actually started in 1.8.0-alpha0 but 1.8 was renamed to 2 before GA. + *

+ * Logback moved to Slf4j 2 in version 1.3, whereas Log4j 2 provides specific + * artifacts for each Slf4j version. + *

+ * Given the above: + * * Slf4j implementations must use the same major version of `slf4j-api` + * * `logback-classic` < 1.3 must use Slf4j 1 + * * `logback-classic` >= 1.3 must use Slf4j 2 + * * `log4j-slf4j-impl` must use Slf4j 1 + * * `log4j-slf4j2-impl` must use Slf4j 2 + */ +@CacheableRule +public abstract class Slf4jVersionCompatibilityRule extends CapabilityDefinitionRule { + + @Inject + public Slf4jVersionCompatibilityRule(CapabilityDefinition capabilityDefinition) { + super(capabilityDefinition); + } + + @Override + protected String getVersion(ModuleVersionIdentifier id) { + switch (id.getGroup()) { + case "org.slf4j": + return (id.getVersion().startsWith("1.")) ? "1.0" : "2.0"; + case "ch.qos.logback": + return id.getVersion().matches("^(0\\.|1\\.[0-2]\\.)") ? "1.0" : "2.0"; + case "org.apache.logging.log4j": + return id.getName().equals("log4-slf4j-impl") ? "1.0" : "2.0"; + default: + throw new IllegalArgumentException("Unexpected component"); + } + } + + @Override + protected void additionalAdjustments(ModuleVersionIdentifier id, VariantMetadata variant) { + String rejectedVersions; + switch (getVersion(id)) { + case"1.0": + rejectedVersions = "[1.8.0-alpha0,)"; break; + case "2.0": + rejectedVersions = "[,1.8.0-alpha0)"; break; + default: + throw new UnsupportedOperationException(); + } + variant.withDependencies(dependencies -> { + for (DirectDependencyMetadata dependency : dependencies) { + if (dependency.getGroup().equals("org.slf4j") && dependency.getName().equals("slf4j-api")) { + dependency.version(version -> { + version.reject(rejectedVersions); + }); + } + } + }); + } +} diff --git a/src/test/groovy/org/gradlex/jvm/dependency/conflict/test/logging/LoggingCapabilitiesPluginDetectionFunctionalTest.groovy b/src/test/groovy/org/gradlex/jvm/dependency/conflict/test/logging/LoggingCapabilitiesPluginDetectionFunctionalTest.groovy index 11ee09d..2b430ec 100644 --- a/src/test/groovy/org/gradlex/jvm/dependency/conflict/test/logging/LoggingCapabilitiesPluginDetectionFunctionalTest.groovy +++ b/src/test/groovy/org/gradlex/jvm/dependency/conflict/test/logging/LoggingCapabilitiesPluginDetectionFunctionalTest.groovy @@ -39,16 +39,16 @@ class LoggingCapabilitiesPluginDetectionFunctionalTest extends AbstractLoggingCa then: outcomeOf(result, ':doIt') == FAILED - conflictOnCapability(result.output, "org.gradlex:slf4j-impl:1.0") + conflictOnCapability(result.output, "org.gradlex:slf4j-impl:$capabilityVersion") where: - first | second - 'org.slf4j:slf4j-simple:1.7.27' | 'ch.qos.logback:logback-classic:1.2.3' - 'org.slf4j:slf4j-simple:1.7.27' | 'org.slf4j:slf4j-log4j12:1.7.27' - 'org.slf4j:slf4j-simple:1.7.27' | 'org.slf4j:slf4j-jcl:1.7.27' - 'org.slf4j:slf4j-simple:1.7.27' | 'org.slf4j:slf4j-jdk14:1.7.27' - 'org.slf4j:slf4j-simple:1.7.27' | 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.0' - 'org.slf4j:slf4j-simple:1.7.27' | 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0' + first | second | capabilityVersion + 'org.slf4j:slf4j-simple:1.7.27' | 'ch.qos.logback:logback-classic:1.2.3' | '1.0' + 'org.slf4j:slf4j-simple:1.7.27' | 'org.slf4j:slf4j-log4j12:1.7.27' | '1.0' + 'org.slf4j:slf4j-simple:1.7.27' | 'org.slf4j:slf4j-jcl:1.7.27' | '1.0' + 'org.slf4j:slf4j-simple:1.7.27' | 'org.slf4j:slf4j-jdk14:1.7.27' | '1.0' + 'org.slf4j:slf4j-simple:1.7.27' | 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.0' | '1.0' + 'org.slf4j:slf4j-simple:1.7.27' | 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0' | '2.0' }