/
DependencyUpdatesReporter.kt
339 lines (309 loc) · 12.5 KB
/
DependencyUpdatesReporter.kt
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
package com.github.benmanes.gradle.versions.updates
import com.github.benmanes.gradle.versions.reporter.HtmlReporter
import com.github.benmanes.gradle.versions.reporter.JsonReporter
import com.github.benmanes.gradle.versions.reporter.PlainTextReporter
import com.github.benmanes.gradle.versions.reporter.Reporter
import com.github.benmanes.gradle.versions.reporter.XmlReporter
import com.github.benmanes.gradle.versions.reporter.result.DependenciesGroup
import com.github.benmanes.gradle.versions.reporter.result.Dependency
import com.github.benmanes.gradle.versions.reporter.result.DependencyLatest
import com.github.benmanes.gradle.versions.reporter.result.DependencyOutdated
import com.github.benmanes.gradle.versions.reporter.result.DependencyUnresolved
import com.github.benmanes.gradle.versions.reporter.result.Result
import com.github.benmanes.gradle.versions.reporter.result.VersionAvailable
import com.github.benmanes.gradle.versions.updates.gradle.GradleReleaseChannel
import com.github.benmanes.gradle.versions.updates.gradle.GradleUpdateChecker
import com.github.benmanes.gradle.versions.updates.gradle.GradleUpdateResult
import com.github.benmanes.gradle.versions.updates.gradle.GradleUpdateResults
import org.gradle.api.Project
import org.gradle.api.artifacts.ModuleVersionSelector
import org.gradle.api.artifacts.UnresolvedDependency
import java.io.File
import java.io.PrintStream
import java.io.PrintWriter
import java.io.StringWriter
import java.util.TreeSet
/**
* Sorts and writes the resolved dependency reports.
*
* @property project The project evaluated against.
* @property revision The revision strategy evaluated with.
* @property outputFormatterArgument The output formatter strategy evaluated with.
* @property outputDir The outputDir for report.
* @property reportfileName The filename of the report file.
* @property currentVersions The current versions of each dependency declared in the project(s).
* @property latestVersions The latest versions of each dependency (as scoped by the revision level).
* @property upToDateVersions The dependencies that are up to date (same as latest found).
* @property downgradeVersions The dependencies that exceed the latest found (e.g. may not want SNAPSHOTs).
* @property upgradeVersions The dependencies where upgrades were found (below latest found).
* @property undeclared The dependencies that were declared without version.
* @property unresolved The dependencies that could not be resolved.
* @property projectUrls Project urls of maven dependencies.
* @property gradleUpdateChecker Facade object to access information about running gradle versions
* and gradle updates.
* @property gradleReleaseChannel The gradle release channel to use for reporting.
*
*/
class DependencyUpdatesReporter(
val project: Project,
val revision: String,
private val outputFormatterArgument: OutputFormatterArgument,
val outputDir: String,
val reportfileName: String?,
val currentVersions: Map<Map<String, String>, Coordinate>,
val latestVersions: Map<Map<String, String>, Coordinate>,
val upToDateVersions: Map<Map<String, String>, Coordinate>,
val downgradeVersions: Map<Map<String, String>, Coordinate>,
val upgradeVersions: Map<Map<String, String>, Coordinate>,
val undeclared: Set<Coordinate>,
val unresolved: Set<UnresolvedDependency>,
val projectUrls: Map<Map<String, String>, String>,
val gradleUpdateChecker: GradleUpdateChecker,
val gradleReleaseChannel: String,
) {
@Synchronized
fun write() {
if (outputFormatterArgument !is OutputFormatterArgument.CustomAction) {
val plainTextReporter = PlainTextReporter(
project, revision, gradleReleaseChannel
)
plainTextReporter.write(System.out, buildBaseObject())
}
if (outputFormatterArgument is OutputFormatterArgument.BuiltIn && outputFormatterArgument.formatterNames.isEmpty()) {
project.logger.lifecycle("Skip generating report to file (outputFormatter is empty)")
return
}
when (outputFormatterArgument) {
is OutputFormatterArgument.BuiltIn -> {
for (it in outputFormatterArgument.formatterNames.split(",")) {
generateFileReport(getOutputReporter(it))
}
}
is OutputFormatterArgument.CustomReporter -> {
generateFileReport(outputFormatterArgument.reporter)
}
is OutputFormatterArgument.CustomAction -> {
val result = buildBaseObject()
outputFormatterArgument.action.execute(result)
}
}
}
private fun generateFileReport(reporter: Reporter) {
val fileName = File(outputDir, reportfileName + "." + reporter.getFileExtension())
project.file(outputDir).mkdirs()
val outputFile = project.file(fileName)
val stream = PrintStream(outputFile)
val result = buildBaseObject()
reporter.write(stream, result)
stream.close()
project.logger.lifecycle("\nGenerated report file $fileName")
}
private fun getOutputReporter(formatterOriginal: String): Reporter {
return when (formatterOriginal.replace("\\s", "")) {
"json" -> JsonReporter(project, revision, gradleReleaseChannel)
"xml" -> XmlReporter(project, revision, gradleReleaseChannel)
"html" -> HtmlReporter(project, revision, gradleReleaseChannel)
else -> PlainTextReporter(project, revision, gradleReleaseChannel)
}
}
private fun buildBaseObject(): Result {
val sortedCurrent = buildCurrentGroup()
val sortedOutdated = buildOutdatedGroup()
val sortedExceeded = buildExceededGroup()
val sortedUndeclared = buildUndeclaredGroup()
val sortedUnresolved = buildUnresolvedGroup()
val count = sortedCurrent.size +
sortedOutdated.size +
sortedExceeded.size +
sortedUndeclared.size +
sortedUnresolved.size
return buildObject(
count = count,
currentGroup = buildDependenciesGroup(sortedCurrent),
outdatedGroup = buildDependenciesGroup(sortedOutdated),
exceededGroup = buildDependenciesGroup(sortedExceeded),
undeclaredGroup = buildDependenciesGroup(sortedUndeclared),
unresolvedGroup = buildDependenciesGroup(sortedUnresolved),
gradleUpdateResults = buildGradleUpdateResults(),
)
}
/**
* Create a [GradleUpdateResults] object from the information provided by the [GradleUpdateChecker]
* @return filled out object instance
*/
private fun buildGradleUpdateResults(): GradleUpdateResults {
val enabled = gradleUpdateChecker.enabled
return GradleUpdateResults(
enabled = enabled,
running = GradleUpdateResult(
enabled = enabled,
running = gradleUpdateChecker.getRunningGradleVersion(),
release = gradleUpdateChecker.getRunningGradleVersion(),
),
current = GradleUpdateResult(
enabled = enabled,
running = gradleUpdateChecker.getRunningGradleVersion(),
release = gradleUpdateChecker.getCurrentGradleVersion(),
),
releaseCandidate = GradleUpdateResult(
enabled = enabled &&
(
gradleReleaseChannel == GradleReleaseChannel.RELEASE_CANDIDATE.id ||
gradleReleaseChannel == GradleReleaseChannel.NIGHTLY.id
),
running = gradleUpdateChecker.getRunningGradleVersion(),
release = gradleUpdateChecker.getReleaseCandidateGradleVersion(),
),
nightly = GradleUpdateResult(
enabled = enabled && (gradleReleaseChannel == GradleReleaseChannel.NIGHTLY.id),
running = gradleUpdateChecker.getRunningGradleVersion(),
release = gradleUpdateChecker.getNightlyGradleVersion(),
),
)
}
private fun buildCurrentGroup(): Set<Dependency> {
return sortByGroupAndName(upToDateVersions)
.map { dep ->
updateKey(dep.key as HashMap)
buildDependency(dep.value, dep.key)
}.toSortedSet() as TreeSet<Dependency>
}
private fun buildOutdatedGroup(): Set<DependencyOutdated> {
return sortByGroupAndName(upgradeVersions)
.map { dep ->
updateKey(dep.key as HashMap)
buildOutdatedDependency(dep.value, dep.key)
}.toSortedSet() as TreeSet<DependencyOutdated>
}
private fun buildExceededGroup(): Set<DependencyLatest> {
return sortByGroupAndName(downgradeVersions)
.map { dep ->
updateKey(dep.key as HashMap)
buildExceededDependency(dep.value, dep.key)
}.toSortedSet() as TreeSet<DependencyLatest>
}
private fun buildUndeclaredGroup(): Set<Dependency> {
return undeclared
.map { coordinate ->
Dependency(coordinate.groupId, coordinate.artifactId)
}.toSortedSet() as TreeSet<Dependency>
}
private fun buildUnresolvedGroup(): Set<DependencyUnresolved> {
return unresolved
.sortedWith { a, b ->
compareKeys(keyOf(a.selector), keyOf(b.selector))
}.map { dep ->
val stringWriter = StringWriter()
dep.problem.printStackTrace(PrintWriter(stringWriter))
val message = stringWriter.toString()
buildUnresolvedDependency(dep.selector, message)
}.toSortedSet() as TreeSet<DependencyUnresolved>
}
private fun buildDependency(
coordinate: Coordinate,
key: Map<String, String>
): Dependency {
return Dependency(
group = key["group"],
name = key["name"],
version = coordinate.version,
projectUrl = projectUrls[key],
userReason = coordinate.userReason,
)
}
private fun buildExceededDependency(
coordinate: Coordinate,
key: Map<String, String>
): DependencyLatest {
return DependencyLatest(
group = key["group"],
name = key["name"],
version = coordinate.version,
projectUrl = projectUrls[key],
userReason = coordinate.userReason,
latest = latestVersions[key]?.version.orEmpty(),
)
}
private fun buildUnresolvedDependency(
selector: ModuleVersionSelector,
message: String
): DependencyUnresolved {
return DependencyUnresolved(
group = selector.group,
name = selector.name,
version = currentVersions[keyOf(selector)]?.version,
projectUrl = latestVersions[keyOf(selector)]?.version, // TODO not sure?
userReason = currentVersions[keyOf(selector)]?.userReason,
reason = message,
)
}
private fun buildOutdatedDependency(
coordinate: Coordinate,
key: Map<String, String>
): DependencyOutdated {
val laterVersion = latestVersions[key]?.version
val available = when (revision) {
"milestone" -> VersionAvailable(milestone = laterVersion)
"integration" -> VersionAvailable(integration = laterVersion)
else -> VersionAvailable(release = laterVersion)
}
return DependencyOutdated(
group = key["group"],
name = key["name"],
version = coordinate.version,
projectUrl = projectUrls[key],
userReason = coordinate.userReason,
available = available,
)
}
companion object {
private fun updateKey(existingKey: HashMap<String, String>) {
val index = existingKey["name"]?.lastIndexOf("[") ?: -1
if (index == -1) {
existingKey["name"] = existingKey["name"].orEmpty()
} else {
existingKey["name"] = existingKey["name"].orEmpty().substring(0, index)
}
}
private fun buildObject(
count: Int,
currentGroup: DependenciesGroup<Dependency>,
outdatedGroup: DependenciesGroup<DependencyOutdated>,
exceededGroup: DependenciesGroup<DependencyLatest>,
undeclaredGroup: DependenciesGroup<Dependency>,
unresolvedGroup: DependenciesGroup<DependencyUnresolved>,
gradleUpdateResults: GradleUpdateResults,
): Result {
return Result(
count = count,
current = currentGroup,
outdated = outdatedGroup,
exceeded = exceededGroup,
undeclared = undeclaredGroup,
unresolved = unresolvedGroup,
gradle = gradleUpdateResults
)
}
private fun <T : Dependency> buildDependenciesGroup(dependencies: Set<T>): DependenciesGroup<T> {
return DependenciesGroup<T>(dependencies.size, dependencies)
}
private fun sortByGroupAndName(
dependencies: Map<Map<String, String>, Coordinate>
): Map<Map<String, String>, Coordinate> {
return dependencies.toSortedMap { a, b ->
compareKeys(a, b)
}
}
/** Compares the dependency keys. */
private fun compareKeys(a: Map<String, String>, b: Map<String, String>): Int {
return if (a["group"] == b["group"]) {
a["name"].orEmpty().compareTo(b["name"].orEmpty())
} else {
a["group"].orEmpty().compareTo(b["group"].orEmpty())
}
}
private fun keyOf(dependency: ModuleVersionSelector): Map<String, String> {
return mapOf("group" to dependency.group, "name" to dependency.name)
}
}
}