-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathDependencyConflictChecker.gradle
132 lines (119 loc) · 4.72 KB
/
DependencyConflictChecker.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
class DependencyConflictChecker implements Plugin<Project> {
@Override
void apply(Project project) {
project.configurations.compileClasspath.incoming.afterResolve {
ResolvedComponentResult resolvedTreeRoot = it.resolutionResult.getRoot()
resolvedTreeRoot.getDependencies().each {
if (it instanceof ResolvedDependencyResult) {
ResolvedDependencyResult subRoot = it
Set<ResolvedComponentResult> resolvedConflicts = extractResolvedConflictsFromTree(subRoot)
resolvedConflicts.each { dep ->
def id = dep.moduleVersion
def selectionReason = dep.selectionReason.description
if (id != null && !isConflictFromCurrentSubtree(subRoot, dep)) {
List<Version> versions = extractVersionsFromSelectionDescription(selectionReason)
if (!isConflictInPatchVersion(versions)) {
throw new GradleException(
"Not accepted version conflict in: \n" +
"\t${id.group}:${id.name} ${selectionReason}\n"
)
} else {
project.logger.log(
LogLevel.WARN,
"Found conflict in ${id.group}:${id.name} ${selectionReason} in patch part of version."
)
}
}
}
}
}
}
}
boolean isConflictFromCurrentSubtree(ResolvedDependencyResult root, ResolvedComponentResult search) {
boolean result = false
traverseNode(root, new HashSet<>(), {
if (it.getRequested().matchesStrictly(search.id)) {
result = true
}
} as Closure<Void>)
return result
}
/**
* Collect all dependencies which versions was changed during dependencies resolution
* @param root - resolved dependencies tree
* @return collected conflicts
*/
Set<ResolvedComponentResult> extractResolvedConflictsFromTree(ResolvedDependencyResult root) {
Set<ResolvedComponentResult> result = new HashSet<>()
traverseNode(root, new HashSet<>(), {
if(it.selected.selectionReason.isConflictResolution()
&& !it.selected.selectionReason.isConstrained()
&& !it.requested.matchesStrictly(it.selected.id)) {
result.add(it.selected)
}
} as Closure<Void>)
return result
}
/**
* recursive tree traversing
* @param resolved - current subtree to traverse
* @param visited - set of nodes that already visited
* @param check - action to perform on current dependency
*/
void traverseNode(ResolvedDependencyResult resolved, Set<ResolvedDependencyResult> visited, Closure<Void> check) {
ResolvedComponentResult node = resolved.selected
if(!visited.add(resolved)) {
return
}
if(node.getDependencies().size() > 0) {
node.getDependencies().each {
if(it instanceof ResolvedDependencyResult) {
traverseNode(it, visited, check)
}
}
}
check(resolved)
}
boolean isConflictInPatchVersion(List<Version> versions) {
for (i in 1..<versions.size()) {
Version first = versions.get(0)
Version cur = versions.get(i)
if(first.major != cur.major) {
return false
}
if(first.minor != cur.minor) {
return false
}
}
return true
}
private List<Version> extractVersionsFromSelectionDescription(String desc) {
List<Version> versions = new LinkedList<>()
desc.replace("between versions", "").replace("and", "").trim().split("\\s+").each {
versions.add(new Version(it))
}
versions
}
class Version {
String major = ""
String minor = ""
String patch = ""
Version(String version) {
String[] parts = version.split("\\.")
if(parts.size() > 0) {
major = parts[0]
if(parts.size() > 1) {
minor = parts[1]
}
if(parts.size() > 2) {
patch = parts[2]
}
}
}
@Override
String toString() {
return major + "." + minor + "." + patch
}
}
}
apply plugin: DependencyConflictChecker