Skip to content

Commit ec1d92d

Browse files
britterjjohannes
andauthored
Implement patch DSL based on variant attribute matching (#253)
Instead of matching for variant names like `runtime` or `compile`, the patch DSL now correctly matches the Usage and Category attribute values per variant before modifying a variant. This is more correct, because components published with Gradle module metadata may have variants that don't follow the "standard" names, e.g. Guava uses variant names like jreRuntimeElements and jreApiElements. Resolves #238 Co-authored-by: Jendrik Johannes <[email protected]>
1 parent be4f1f0 commit ec1d92d

File tree

10 files changed

+456
-30
lines changed

10 files changed

+456
-30
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# JVM Dependency Conflict Resolution Gradle plugin - Changelog
22

3+
## Version 2.4
4+
* [Fix] [#238](https://github.com/gradlex-org/jvm-dependency-conflict-resolution/issues/238) Patch DSL now works for dependencies with non-standard variant names (e.g. com.google.guava).
5+
36
## Version 2.3
47
* [New Rule] [#66](https://github.com/gradlex-org/jvm-dependency-conflict-resolution/issues/66) itext:itext / com.lowagie:itext (Thanks [Björn Kautler](https://github.com/Vampire) for reporting)
58
* [New Rule] [#222](https://github.com/gradlex-org/jvm-dependency-conflict-resolution/issues/222) dk.brics.automaton:automaton / dk.brics:automaton (Thanks [Ketan Padegaonkar](https://github.com/ketan) for reporting)

src/main/java/org/gradlex/jvm/dependency/conflict/resolution/rules/AddApiDependencyMetadataRule.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
import org.gradle.api.artifacts.CacheableRule;
2020
import org.gradle.api.artifacts.ComponentMetadataContext;
2121
import org.gradle.api.artifacts.ComponentMetadataRule;
22+
import org.gradle.api.attributes.Category;
23+
import org.gradle.api.attributes.Usage;
2224

2325
import javax.inject.Inject;
2426

27+
import static org.gradlex.jvm.dependency.conflict.resolution.rules.VariantSelection.allVariantsMatching;
28+
2529
/**
2630
* See:
2731
* <a href="https://docs.gradle.org/current/userguide/component_metadata_rules.html#fixing_wrong_dependency_details">
28-
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
32+
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
2933
*/
3034
@CacheableRule
3135
public abstract class AddApiDependencyMetadataRule implements ComponentMetadataRule {
@@ -39,6 +43,8 @@ public AddApiDependencyMetadataRule(String dependency) {
3943

4044
@Override
4145
public void execute(ComponentMetadataContext context) {
42-
context.getDetails().allVariants(v -> v.withDependencies(d -> d.add(dependency)));
46+
allVariantsMatching(context,
47+
id -> (id.matches(Usage.USAGE_ATTRIBUTE, Usage.JAVA_API) || id.matches(Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME)) && id.matches(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY),
48+
v -> v.withDependencies(d -> d.add(dependency)));
4349
}
4450
}

src/main/java/org/gradlex/jvm/dependency/conflict/resolution/rules/AddCompileOnlyApiDependencyMetadataRule.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
import org.gradle.api.artifacts.CacheableRule;
2020
import org.gradle.api.artifacts.ComponentMetadataContext;
2121
import org.gradle.api.artifacts.ComponentMetadataRule;
22+
import org.gradle.api.attributes.Category;
23+
import org.gradle.api.attributes.Usage;
2224

2325
import javax.inject.Inject;
2426

27+
import static org.gradlex.jvm.dependency.conflict.resolution.rules.VariantSelection.allVariantsMatching;
28+
2529
/**
2630
* See:
2731
* <a href="https://docs.gradle.org/current/userguide/component_metadata_rules.html#fixing_wrong_dependency_details">
28-
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
32+
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
2933
*/
3034
@CacheableRule
3135
public abstract class AddCompileOnlyApiDependencyMetadataRule implements ComponentMetadataRule {
@@ -39,7 +43,8 @@ public AddCompileOnlyApiDependencyMetadataRule(String dependency) {
3943

4044
@Override
4145
public void execute(ComponentMetadataContext context) {
42-
context.getDetails().withVariant("compile", v -> v.withDependencies(d -> d.add(dependency))); // .pom
43-
context.getDetails().withVariant("apiElements", v -> v.withDependencies(d -> d.add(dependency))); // .module
46+
allVariantsMatching(context,
47+
id -> (id.matches(Usage.USAGE_ATTRIBUTE, Usage.JAVA_API) && id.matches(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY)),
48+
v -> v.withDependencies(d -> d.add(dependency)));
4449
}
4550
}

src/main/java/org/gradlex/jvm/dependency/conflict/resolution/rules/AddRuntimeOnlyDependencyMetadataRule.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
import org.gradle.api.artifacts.CacheableRule;
2020
import org.gradle.api.artifacts.ComponentMetadataContext;
2121
import org.gradle.api.artifacts.ComponentMetadataRule;
22+
import org.gradle.api.attributes.Category;
23+
import org.gradle.api.attributes.Usage;
2224

2325
import javax.inject.Inject;
2426

27+
import static org.gradlex.jvm.dependency.conflict.resolution.rules.VariantSelection.allVariantsMatching;
28+
2529
/**
2630
* See:
2731
* <a href="https://docs.gradle.org/current/userguide/component_metadata_rules.html#fixing_wrong_dependency_details">
28-
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
32+
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
2933
*/
3034
@CacheableRule
3135
public abstract class AddRuntimeOnlyDependencyMetadataRule implements ComponentMetadataRule {
@@ -39,7 +43,8 @@ public AddRuntimeOnlyDependencyMetadataRule(String dependency) {
3943

4044
@Override
4145
public void execute(ComponentMetadataContext context) {
42-
context.getDetails().withVariant("runtime", v -> v.withDependencies(d -> d.add(dependency))); // .pom
43-
context.getDetails().withVariant("runtimeElements", v -> v.withDependencies(d -> d.add(dependency))); // .module
46+
allVariantsMatching(context,
47+
id -> id.matches(Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME) && id.matches(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY),
48+
v -> v.withDependencies(d -> d.add(dependency)));
4449
}
4550
}

src/main/java/org/gradlex/jvm/dependency/conflict/resolution/rules/ReduceToCompileOnlyApiDependencyMetadataRule.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@
1919
import org.gradle.api.artifacts.CacheableRule;
2020
import org.gradle.api.artifacts.ComponentMetadataContext;
2121
import org.gradle.api.artifacts.ComponentMetadataRule;
22+
import org.gradle.api.attributes.Category;
23+
import org.gradle.api.attributes.Usage;
2224

2325
import javax.inject.Inject;
2426
import java.util.stream.Collectors;
2527

28+
import static org.gradlex.jvm.dependency.conflict.resolution.rules.VariantSelection.allVariantsMatching;
29+
2630
/**
2731
* See:
2832
* <a href="https://docs.gradle.org/current/userguide/component_metadata_rules.html#fixing_wrong_dependency_details">
29-
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
33+
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
3034
*/
3135
@CacheableRule
3236
public abstract class ReduceToCompileOnlyApiDependencyMetadataRule implements ComponentMetadataRule {
@@ -40,7 +44,8 @@ public ReduceToCompileOnlyApiDependencyMetadataRule(String dependency) {
4044

4145
@Override
4246
public void execute(ComponentMetadataContext context) {
43-
context.getDetails().withVariant("runtime", v -> v.withDependencies(d -> d.removeAll(d.stream().filter(it -> dependency.equals(it.getModule().toString())).collect(Collectors.toList())))); // .pom
44-
context.getDetails().withVariant("runtimeElements", v -> v.withDependencies(d -> d.removeAll(d.stream().filter(it -> dependency.equals(it.getModule().toString())).collect(Collectors.toList())))); // .module
47+
allVariantsMatching(context,
48+
id -> id.matches(Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME) && id.matches(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY),
49+
v -> v.withDependencies(d -> d.removeAll(d.stream().filter(it -> dependency.equals(it.getModule().toString())).collect(Collectors.toList()))));
4550
}
4651
}

src/main/java/org/gradlex/jvm/dependency/conflict/resolution/rules/ReduceToRuntimeOnlyDependencyMetadataRule.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
import org.gradle.api.artifacts.CacheableRule;
2020
import org.gradle.api.artifacts.ComponentMetadataContext;
2121
import org.gradle.api.artifacts.ComponentMetadataRule;
22+
import org.gradle.api.attributes.Category;
23+
import org.gradle.api.attributes.Usage;
2224

2325
import javax.inject.Inject;
2426
import java.util.stream.Collectors;
2527

28+
import static org.gradlex.jvm.dependency.conflict.resolution.rules.VariantSelection.allVariantsMatching;
29+
2630
/**
2731
* See:
2832
* <a href="https://docs.gradle.org/current/userguide/component_metadata_rules.html#fixing_wrong_dependency_details">
@@ -40,7 +44,8 @@ public ReduceToRuntimeOnlyDependencyMetadataRule(String dependency) {
4044

4145
@Override
4246
public void execute(ComponentMetadataContext context) {
43-
context.getDetails().withVariant("compile", v -> v.withDependencies(d -> d.removeAll(d.stream().filter(it -> dependency.equals(it.getModule().toString())).collect(Collectors.toList())))); // .pom
44-
context.getDetails().withVariant("apiElements", v -> v.withDependencies(d -> d.removeAll(d.stream().filter(it -> dependency.equals(it.getModule().toString())).collect(Collectors.toList())))); // .module
47+
allVariantsMatching(context,
48+
id -> id.matches(Usage.USAGE_ATTRIBUTE, Usage.JAVA_API) && id.matches(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY),
49+
v -> v.withDependencies(d -> d.removeAll(d.stream().filter(it -> dependency.equals(it.getModule().toString())).collect(Collectors.toList())))); // .module
4550
}
4651
}

src/main/java/org/gradlex/jvm/dependency/conflict/resolution/rules/RemoveDependencyMetadataRule.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@
1919
import org.gradle.api.artifacts.CacheableRule;
2020
import org.gradle.api.artifacts.ComponentMetadataContext;
2121
import org.gradle.api.artifacts.ComponentMetadataRule;
22+
import org.gradle.api.attributes.Category;
23+
import org.gradle.api.attributes.Usage;
2224

2325
import javax.inject.Inject;
2426
import java.util.stream.Collectors;
2527

28+
import static org.gradlex.jvm.dependency.conflict.resolution.rules.VariantSelection.allVariantsMatching;
29+
2630
/**
2731
* See:
2832
* <a href="https://docs.gradle.org/current/userguide/component_metadata_rules.html#fixing_wrong_dependency_details">
29-
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
33+
* component_metadata_rules.html#fixing_wrong_dependency_details</a>
3034
*/
3135
@CacheableRule
3236
public abstract class RemoveDependencyMetadataRule implements ComponentMetadataRule {
@@ -40,7 +44,8 @@ public RemoveDependencyMetadataRule(String dependency) {
4044

4145
@Override
4246
public void execute(ComponentMetadataContext context) {
43-
context.getDetails().allVariants(v -> v.withDependencies(d ->
44-
d.removeAll(d.stream().filter(it -> dependency.equals(it.getModule().toString())).collect(Collectors.toList()))));
47+
allVariantsMatching(context,
48+
id -> (id.matches(Usage.USAGE_ATTRIBUTE, Usage.JAVA_API) || id.matches(Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME)) && id.matches(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY),
49+
v -> v.withDependencies(d -> d.removeAll(d.stream().filter(it -> dependency.equals(it.getModule().toString())).collect(Collectors.toList()))));
4550
}
4651
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright the GradleX team.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.gradlex.jvm.dependency.conflict.resolution.rules;
18+
19+
import org.gradle.api.Named;
20+
import org.gradle.api.attributes.Attribute;
21+
22+
import java.util.Map;
23+
import java.util.Objects;
24+
25+
class VariantIdentification {
26+
27+
private final Map<String, String> attributes;
28+
29+
VariantIdentification(Map<String, String> attributes) {
30+
this.attributes = attributes;
31+
}
32+
33+
boolean matches(Attribute<? extends Named> attribute, String value) {
34+
return matches(attribute.getName(), value);
35+
}
36+
37+
boolean matches(String attribute, String value) {
38+
return Objects.equals(attributes.get(attribute), value);
39+
}
40+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright the GradleX team.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.gradlex.jvm.dependency.conflict.resolution.rules;
18+
19+
import org.gradle.api.Action;
20+
import org.gradle.api.artifacts.ComponentMetadataContext;
21+
import org.gradle.api.artifacts.VariantMetadata;
22+
import org.gradle.api.attributes.Attribute;
23+
import org.gradle.api.attributes.AttributeContainer;
24+
import org.gradle.api.attributes.Category;
25+
import org.gradle.api.attributes.LibraryElements;
26+
import org.gradle.api.attributes.Usage;
27+
import org.gradle.internal.component.external.model.ModuleComponentResolveMetadata;
28+
import org.gradle.internal.component.model.VariantResolveMetadata;
29+
30+
import java.lang.reflect.Field;
31+
import java.lang.reflect.Method;
32+
import java.util.HashMap;
33+
import java.util.List;
34+
import java.util.Map;
35+
import java.util.function.Predicate;
36+
import java.util.stream.Collectors;
37+
import java.util.stream.Stream;
38+
39+
import static java.util.Objects.requireNonNull;
40+
41+
final class VariantSelection {
42+
43+
static class MavenVariant {
44+
String name;
45+
Map<String, String> attributes = new HashMap<>();
46+
47+
MavenVariant(String name) {
48+
this.name = name;
49+
}
50+
51+
MavenVariant attribute(String name, String value) {
52+
attributes.put(name, value);
53+
return this;
54+
}
55+
}
56+
57+
static final MavenVariant MAVEN_RUNTIME_VARIANT = new MavenVariant("runtime")
58+
.attribute(Usage.USAGE_ATTRIBUTE.getName(), Usage.JAVA_RUNTIME)
59+
.attribute(Category.CATEGORY_ATTRIBUTE.getName(), Category.LIBRARY)
60+
.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE.getName(), LibraryElements.JAR);
61+
static final MavenVariant MAVEN_COMPILE_VARIANT = new MavenVariant("compile")
62+
.attribute(Usage.USAGE_ATTRIBUTE.getName(), Usage.JAVA_API)
63+
.attribute(Category.CATEGORY_ATTRIBUTE.getName(), Category.LIBRARY)
64+
.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE.getName(), LibraryElements.JAR);
65+
66+
67+
private VariantSelection() { }
68+
69+
static void allVariantsMatching(ComponentMetadataContext context, Predicate<VariantIdentification> id, Action<? super VariantMetadata> action) {
70+
List<String> variantNames = discoverNames(context, id);
71+
variantNames.forEach(variantName -> context.getDetails().withVariant(variantName, action));
72+
}
73+
74+
private static List<String> discoverNames(ComponentMetadataContext context, Predicate<VariantIdentification> id) {
75+
ModuleComponentResolveMetadata metadata = extractMetadataFromContext(context);
76+
List<VariantResolveMetadata> variants = getVariants(metadata);
77+
78+
if (variants.isEmpty()) {
79+
return Stream.of(MAVEN_RUNTIME_VARIANT, MAVEN_COMPILE_VARIANT)
80+
.filter(v -> id.test(new VariantIdentification(v.attributes)))
81+
.map(v -> v.name)
82+
.collect(Collectors.toList());
83+
} else {
84+
return variants.stream()
85+
.filter(v -> id.test(new VariantIdentification(toMap(getAttributes(v)))))
86+
.map(VariantResolveMetadata::getName)
87+
.collect(Collectors.toList());
88+
}
89+
}
90+
91+
private static Map<String, String> toMap(AttributeContainer attributes) {
92+
return attributes.keySet().stream().collect(Collectors
93+
.toMap(Attribute::getName, k -> requireNonNull(attributes.getAttribute(k)).toString()));
94+
}
95+
96+
private static AttributeContainer getAttributes(VariantResolveMetadata metadata) {
97+
try {
98+
Method getVariants = VariantResolveMetadata.class.getDeclaredMethod("getAttributes");
99+
return (AttributeContainer) getVariants.invoke(metadata);
100+
} catch (ReflectiveOperationException e) {
101+
throw new RuntimeException(e);
102+
}
103+
}
104+
105+
private static List<VariantResolveMetadata> getVariants(ModuleComponentResolveMetadata metadata) {
106+
try {
107+
Method getVariants = ModuleComponentResolveMetadata.class.getDeclaredMethod("getVariants");
108+
@SuppressWarnings("unchecked")
109+
List<VariantResolveMetadata> variants = (List<VariantResolveMetadata>) getVariants.invoke(metadata);
110+
return variants;
111+
} catch (ReflectiveOperationException e) {
112+
throw new RuntimeException(e);
113+
}
114+
}
115+
116+
private static ModuleComponentResolveMetadata extractMetadataFromContext(ComponentMetadataContext context) {
117+
try {
118+
Field metadataField = context.getClass().getDeclaredField("metadata");
119+
metadataField.setAccessible(true);
120+
return (ModuleComponentResolveMetadata) metadataField.get(context);
121+
} catch (ReflectiveOperationException e) {
122+
throw new RuntimeException(e);
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)