Skip to content

Commit

Permalink
Merge pull request #32 from f4lco/develop
Browse files Browse the repository at this point in the history
v0.3.0
  • Loading branch information
f4lco authored Nov 8, 2024
2 parents e29a5c4 + 467b2dc commit 1babf64
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 4 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ BUILD SUCCESSFUL in 28s

## Changelog

### 0.3.0 (2024-11-08)

Thanks to @Breefield the plugin now writes a JSON report in `build/reports` which contains a machine-readable report of Libyears per dependency 🚀

### 0.2.0 (2024-10-16)

@Breefield added the `maxTransitiveDepth` toggle to put an upper bound to the depth of dependency traversal.
Expand Down
2 changes: 2 additions & 0 deletions libyear-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:4.8.1")
implementation("com.fasterxml.jackson.core:jackson-databind:2.9.9")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.9")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9")

testImplementation("com.squareup.okhttp3:mockwebserver:4.8.1")
testImplementation("org.mockito:mockito-core:3.7.7")
Expand All @@ -97,6 +98,7 @@ tasks.withType(Test::class) {
testLogging {
events("started")
showExceptions = true
showStandardStreams = true
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,24 @@ internal class LibYearPluginTest {
.extracting { it?.outcome }.isEqualTo(TaskOutcome.FAILED)
}

private fun withGradleRunner() = GradleRunner.create().apply {
@Test
fun testReportLibyear() {
setUpProject("valid.gradle.kts")
withGradleRunner("reportLibyear").build()
val libyearJsonFile = project.resolve("build/reports/libyear/libyear.json").toFile().readText()
val expectedJson = javaClass.getResourceAsStream("expectedReport.json")?.bufferedReader()?.use { it.readText() }

// Replace lagDays values with 0 using regex so reports match despite age
val lagDaysRegex = "\"lagDays\":\\s*\\d+".toRegex()
val normalizedLibyearJson = libyearJsonFile.replace(lagDaysRegex, "\"lagDays\": 0")
val normalizedExpectedJson = expectedJson?.replace(lagDaysRegex, "\"lagDays\": 0")

assertThat(normalizedLibyearJson).isEqualToIgnoringWhitespace(normalizedExpectedJson)
}

private fun withGradleRunner(command: String = "dependencies") = GradleRunner.create().apply {
withProjectDir(project.toFile())
withArguments("dependencies")
withArguments(command)
withPluginClasspath()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"collected" : [ {
"module" : {
"group" : "org.apache.commons",
"name" : "commons-text",
"version" : "1.9"
},
"lag_days" : 1361
}, {
"module" : {
"group" : "org.apache.commons",
"name" : "commons-collections4",
"version" : "4.4"
},
"lag_days" : 1806
} ],
"missing_info" : [ ],
"errors" : [ ]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ open class LibYearReportTask : DefaultTask() {
val visitor = ReportingVisitor(project.logger, ageOracle)
DependencyTraversal.visit(it.incoming.resolutionResult.root, visitor, extension.maxTransitiveDepth)
visitor.print()
visitor.saveReportToJson(project)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.libyear.traversal

import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.libyear.sourcing.VersionOracle
import com.libyear.util.formatApproximate
import org.gradle.api.Project
import org.gradle.api.artifacts.ModuleVersionIdentifier
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.internal.artifacts.result.ResolvedComponentResultInternal
import org.gradle.api.logging.Logger
import java.io.File
import java.time.Duration

private data class ReportingInfo(
Expand All @@ -20,6 +25,8 @@ class ReportingVisitor(
) : DependencyVisitor(logger) {

private val collected = mutableListOf<ReportingInfo>()
private val missingInfo = mutableListOf<ModuleVersionIdentifier>()
private val errors = mutableListOf<ModuleVersionIdentifier>()

private val totalAge: Duration get() = collected.map { it.lag }.fold(Duration.ZERO, Duration::plus)

Expand All @@ -31,10 +38,14 @@ class ReportingVisitor(
val age = ageOracle.get(module, repositoryName)

age.onSuccess { info ->
info.update?.let { update ->
val update = info.update
if (update != null) {
collected += ReportingInfo(module, update.lag, update.nextVersion)
} else {
missingInfo += module
}
}.onFailure {
errors += module
logger.error("""Cannot determine dependency age for "$module" and repository "$repositoryName" (reason: ${it::class.simpleName}: ${it.message}).""")
}
}
Expand All @@ -47,17 +58,64 @@ class ReportingVisitor(
}

fun print() {
logger.lifecycle("Collected ${totalAge.formatApproximate()} worth of libyears from ${collected.size} dependencies:")
if (missingInfo.isNotEmpty()) {
logger.lifecycle("Dependencies with no update information available:")
missingInfo.forEach { module ->
logger.lifecycle(" -> ${module.group}:${module.name}:${module.version}")
}
}

if (errors.isNotEmpty()) {
logger.lifecycle("Dependencies with errors during age determination:")
errors.forEach { module ->
logger.lifecycle(" -> ${module.group}:${module.name}:${module.version}")
}
}

if (missingInfo.isNotEmpty() || errors.isNotEmpty()) {
logger.lifecycle("") // Blank line
}

logger.lifecycle("Collected ${totalAge.formatApproximate()} worth of libyears from ${collected.size} dependencies:")
collected.sortedWith(byAgeAndModule()).forEach { dep ->
logger.lifecycle(" -> ${dep.lag.formatApproximate().padEnd(10)} from ${dep.module.module} (${dep.module.version} => ${dep.latestVersion})")
}
}

fun saveReportToJson(project: Project) {
val report = mapOf(
"collected" to collected.map {
mapOf(
"module" to ReportModule(it.module),
"lag_days" to it.lag.toDays()
)
},
"missing_info" to missingInfo.map(::ReportModule),
"errors" to errors.map(::ReportModule)
)
val objectMapper = ObjectMapper().registerKotlinModule().configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
val json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(report)
val reportFile = File(project.buildDir, "reports/libyear/libyear.json")
reportFile.parentFile.mkdirs()
reportFile.writeText(json)
}

private fun byAgeAndModule(): Comparator<ReportingInfo> = compareByDescending<ReportingInfo> { it.lag }.thenComparing(ReportingInfo::module, byModule())

private fun byModule(): Comparator<ModuleVersionIdentifier> = compareBy(
ModuleVersionIdentifier::getGroup,
ModuleVersionIdentifier::getName,
ModuleVersionIdentifier::getVersion
)

/** JSON-serializable subset of [ModuleVersionIdentifier]. **/
data class ReportModule(
val group: String,
val name: String,
val version: String
) {

constructor(mvi: ModuleVersionIdentifier) :
this(mvi.group, mvi.name, mvi.version)
}
}

0 comments on commit 1babf64

Please sign in to comment.