Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.jakewharton.diffuse.diff

import com.jakewharton.diffuse.diffuseTable
import com.jakewharton.diffuse.format.Jar
import com.jakewharton.diffuse.format.TypeDescriptor
import com.jakewharton.diffuse.report.toDiffString
import com.jakewharton.picnic.TextAlignment.MiddleRight
import com.jakewharton.picnic.renderText
import kotlin.collections.iterator

/**
* Diff for bytecode versions across two sets of JARs.
*
* @param versionCounts per-version summary: maps each bytecode version integer to a pair of
* (`oldCount`, `newCount`) where the counts differ, sorted by `version`.
* @param changedClasses classes that exist in both old and new but changed bytecode version, as
* triples of (`descriptor`, `oldVersion`, `newVersion`), sorted by `descriptor`.
*/
internal class BytecodeVersionDiff(
val versionCounts: Map<Version, Pair<Count, Count>>,
val changedClasses: List<Triple<TypeDescriptor, Version, Version>>,
) {
val changed = versionCounts.isNotEmpty() || changedClasses.isNotEmpty()

@JvmInline value class Count(val count: Int)

@JvmInline
value class Version(val version: Int) : Comparable<Version> {
override fun compareTo(other: Version): Int = version.compareTo(other.version)
}
}

internal fun bytecodeVersionDiff(oldJars: List<Jar>, newJars: List<Jar>): BytecodeVersionDiff {
val oldVersionMap: Map<TypeDescriptor, BytecodeVersionDiff.Version> =
oldJars
.flatMap { it.classes }
.associate { it.descriptor to BytecodeVersionDiff.Version(it.bytecodeVersion) }
val newVersionMap: Map<TypeDescriptor, BytecodeVersionDiff.Version> =
newJars
.flatMap { it.classes }
.associate { it.descriptor to BytecodeVersionDiff.Version(it.bytecodeVersion) }

// Classes present in both with different versions.
val changedClasses =
oldVersionMap
.mapNotNull { (descriptor, oldVersion) ->
val newVersion = newVersionMap[descriptor]
if (newVersion != null && newVersion != oldVersion) {
Triple(descriptor, oldVersion, newVersion)
} else {
null
}
}
.sortedBy { it.first }

// Tally per-version counts across all classes in old and new.
val allVersions = (oldVersionMap.values + newVersionMap.values).toSortedSet()
val versionCounts =
allVersions
.associateWith { version ->
val oldCount = oldVersionMap.values.count { it == version }
val newCount = newVersionMap.values.count { it == version }
BytecodeVersionDiff.Count(oldCount) to BytecodeVersionDiff.Count(newCount)
}
.filter { (_, counts) -> counts.first != counts.second }

return BytecodeVersionDiff(versionCounts, changedClasses)
}

internal fun StringBuilder.appendBytecodeVersionDiff(name: String, diff: BytecodeVersionDiff) {
if (!diff.changed) return
appendLine()
appendLine("$name:")
appendLine()

if (diff.versionCounts.isNotEmpty()) {
diffuseTable {
header {
row {
cell("version")
cell("old")
cell("new")
cell("diff")
}
}

body {
cellStyle { alignment = MiddleRight }

for ((version, counts) in diff.versionCounts) {
val (oldCount, newCount) = counts
val net = (newCount.count - oldCount.count).toDiffString()
val added =
(newCount.count - oldCount.count).coerceAtLeast(0).toDiffString(zeroSign = '+')
val removed =
(-(oldCount.count - newCount.count).coerceAtLeast(0)).toDiffString(zeroSign = '-')
row(version.version, oldCount.count, newCount.count, "$net ($added $removed)")
}
}
}
.renderText()
.prependIndent(" ")
.let(::appendLine)
}

if (diff.changedClasses.isNotEmpty()) {
if (diff.versionCounts.isNotEmpty()) {
appendLine()
}
diff.changedClasses.forEach { (descriptor, oldVersion, newVersion) ->
appendLine(" $descriptor: ${oldVersion.version} → ${newVersion.version}")
}
}
appendLine()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ internal class JarsDiff(
val newMapping: ApiMapping,
) {
val classes = componentDiff(oldJars, newJars) { it.classes.map(Class::descriptor) }
val bytecodeVersions =
componentDiff(oldJars, newJars) { jar ->
jar.classes.map { "${it.descriptor}: ${it.bytecodeVersion}" }
}
val bytecodeVersions = bytecodeVersionDiff(oldJars, newJars)
val methods = componentDiff(oldJars, newJars) { it.members.filterIsInstance<Method>() }
val declaredMethods =
componentDiff(oldJars, newJars) { it.declaredMembers.filterIsInstance<Method>() }
Expand Down Expand Up @@ -76,7 +73,7 @@ internal fun JarsDiff.toSummaryTable(name: String) =
internal fun JarsDiff.toDetailReport() = buildString {
// TODO appendComponentDiff("STRINGS", strings)?
appendComponentDiff("CLASSES", classes)
appendComponentDiff("BYTECODE VERSIONS", bytecodeVersions)
appendBytecodeVersionDiff("BYTECODE VERSIONS", bytecodeVersions)
appendComponentDiff("METHODS", methods)
appendComponentDiff("FIELDS", fields)
}
Loading