From 57717b896c02fe6563c057e303a10756c877f1b5 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 26 Mar 2026 11:26:04 +0800 Subject: [PATCH] Show JAR manifest diff details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ```diff OLD: old.jar NEW: new.jar JAR │ old │ new │ diff ───────┼────────────┼────────────┼────── class │ 12.86 MiB │ 12.86 MiB │ 0 B other │ 375.23 KiB │ 375.22 KiB │ -8 B ───────┼────────────┼────────────┼────── total │ 13.23 MiB │ 13.23 MiB │ -8 B CLASSES │ old │ new │ diff ─────────┼───────┼───────┼─────────── classes │ 3308 │ 3308 │ 0 (+0 -0) methods │ 37498 │ 37498 │ 0 (+0 -0) fields │ 15282 │ 15282 │ 0 (+0 -0) ================= ==== JAR ==== ================= size │ diff │ path ──────────┼──────┼────────────────────────────────────────────────── 960 B │ -4 B │ ∆ META-INF/MANIFEST.MF 830 B │ -4 B │ ∆ META-INF/micrometer-registry-statsd.properties ──────────┼──────┼────────────────────────────────────────────────── 1.75 KiB │ -8 B │ (total) ====================== ==== MANIFEST ==== ====================== @@ -2,5 +2,5 @@ Automatic-Module-Name: micrometer.registry.statsd -Implementation-Title: io.micrometer#micrometer-registry-statsd;1.17.0-SN - APSHOT -Implementation-Version: 1.17.0-SNAPSHOT +Implementation-Title: io.micrometer#micrometer-registry-statsd;0.1.0-SNA + PSHOT +Implementation-Version: 0.1.0-SNAPSHOT Built-Status: integration @@ -9,10 +9,10 @@ Build-Timezone: Asia/Shanghai -Build-Date-UTC: 2026-03-26T01:17:09.160902Z -Build-Date: 2026-03-26_09:17:09 +Build-Date-UTC: 2026-03-26T01:44:09.621464Z +Build-Date: 2026-03-26_09:44:09 Gradle-Version: 9.4.1 Module-Source: -Module-Origin: https://github.com/micrometer-metrics/micrometer.git -Change: 50362b5 -Full-Change: 50362b5e874e650ebbde888de300f5949e6720fd -Branch: main +Module-Origin: https://github.com/Goooler/micrometer.git +Change: 0d10057 +Full-Change: 0d100579e6285843e49b398f974cddd06bd843b0 +Branch: shadow-config Build-Host: Gooolers-MacBook-Pro.local ``` --- CHANGELOG.md | 1 + formats/api/formats.api | 3 +- .../com/jakewharton/diffuse/format/Jar.kt | 12 +++- .../com/jakewharton/diffuse/diff/JarDiff.kt | 3 +- .../diffuse/diff/JarManifestDiff.kt | 32 ++++++++++ .../diffuse/report/text/JarDiffTextReport.kt | 7 +++ .../diffuse/JarDiffTextReportTest.kt | 63 +++++++++++++++++++ 7 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 reports/src/main/kotlin/com/jakewharton/diffuse/diff/JarManifestDiff.kt create mode 100644 reports/src/test/kotlin/com/jakewharton/diffuse/JarDiffTextReportTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index bf259062..e8dc8d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ **Added** - Add `--summary-only` flag. - Support diffing bytecode versions for classes. +- Show JAR manifest diff details. **Changed** - Replace `com.jakewharton.diffuse.io.Size` with `me.saket.bytesize.ByteSize` in the APIs. diff --git a/formats/api/formats.api b/formats/api/formats.api index 18eacb00..139ad0a9 100644 --- a/formats/api/formats.api +++ b/formats/api/formats.api @@ -280,11 +280,12 @@ public final class com/jakewharton/diffuse/format/Field : com/jakewharton/diffus public final class com/jakewharton/diffuse/format/Jar : com/jakewharton/diffuse/format/BinaryFormat, com/jakewharton/diffuse/format/CodeBinary { public static final field Companion Lcom/jakewharton/diffuse/format/Jar$Companion; - public synthetic fun (Ljava/lang/String;Lcom/jakewharton/diffuse/format/ArchiveFiles;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lcom/jakewharton/diffuse/format/ArchiveFiles;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getClasses ()Ljava/util/List; public fun getDeclaredMembers ()Ljava/util/List; public fun getFilename ()Ljava/lang/String; public final fun getFiles ()Lcom/jakewharton/diffuse/format/ArchiveFiles; + public final fun getManifest ()Ljava/lang/String; public fun getMembers ()Ljava/util/List; public fun getReferencedMembers ()Ljava/util/List; public static final fun parse (Lcom/jakewharton/diffuse/io/Input;)Lcom/jakewharton/diffuse/format/Jar; diff --git a/formats/src/main/kotlin/com/jakewharton/diffuse/format/Jar.kt b/formats/src/main/kotlin/com/jakewharton/diffuse/format/Jar.kt index d208c586..0a175a83 100644 --- a/formats/src/main/kotlin/com/jakewharton/diffuse/format/Jar.kt +++ b/formats/src/main/kotlin/com/jakewharton/diffuse/format/Jar.kt @@ -8,6 +8,7 @@ import com.jakewharton.diffuse.io.Input class Jar private constructor( override val filename: String?, + val manifest: String, val files: ArchiveFiles, val classes: List, override val declaredMembers: List, @@ -16,6 +17,8 @@ private constructor( override val members = declaredMembers + referencedMembers companion object { + private const val MANIFEST_PATH = "META-INF/MANIFEST.MF" + @JvmStatic @JvmName("parse") fun Input.toJar(): Jar { @@ -30,7 +33,14 @@ private constructor( // Declared methods are likely to reference other declared members. Ensure all are removed. referencedMembers -= declaredMembers - return Jar(name, files, classes, declaredMembers.sorted(), referencedMembers.sorted()) + return Jar( + filename = name, + manifest = zip[MANIFEST_PATH].asInput().toUtf8(), + files = files, + classes = classes, + declaredMembers = declaredMembers.sorted(), + referencedMembers = referencedMembers.sorted(), + ) } } } diff --git a/reports/src/main/kotlin/com/jakewharton/diffuse/diff/JarDiff.kt b/reports/src/main/kotlin/com/jakewharton/diffuse/diff/JarDiff.kt index e2e3e2d2..8a3528b9 100644 --- a/reports/src/main/kotlin/com/jakewharton/diffuse/diff/JarDiff.kt +++ b/reports/src/main/kotlin/com/jakewharton/diffuse/diff/JarDiff.kt @@ -13,8 +13,9 @@ internal class JarDiff( ) : BinaryDiff { val archive = ArchiveFilesDiff(oldJar.files, newJar.files, includeCompressed = false) val jars = JarsDiff(listOf(oldJar), oldMapping, listOf(newJar), newMapping) + val manifest = JarManifestDiff(oldJar.manifest, newJar.manifest) - val changed = jars.changed || archive.changed + val changed = jars.changed || archive.changed || manifest.changed override fun toTextReport(summaryOnly: Boolean): Report = JarDiffTextReport(this, summaryOnly) } diff --git a/reports/src/main/kotlin/com/jakewharton/diffuse/diff/JarManifestDiff.kt b/reports/src/main/kotlin/com/jakewharton/diffuse/diff/JarManifestDiff.kt new file mode 100644 index 00000000..7e4270d3 --- /dev/null +++ b/reports/src/main/kotlin/com/jakewharton/diffuse/diff/JarManifestDiff.kt @@ -0,0 +1,32 @@ +package com.jakewharton.diffuse.diff + +import com.github.difflib.DiffUtils +import com.github.difflib.UnifiedDiffUtils + +internal class JarManifestDiff(oldManifest: String, newManifest: String) { + val diff: List = + if (oldManifest == newManifest) { + emptyList() + } else { + val oldLines = oldManifest.lines() + val newLines = newManifest.lines() + val diff = DiffUtils.diff(oldLines, newLines) + UnifiedDiffUtils.generateUnifiedDiff(MANIFEST_PATH, MANIFEST_PATH, oldLines, diff, 1) + } + + val changed = diff.isNotEmpty() + + internal companion object { + const val MANIFEST_PATH = "META-INF/MANIFEST.MF" + } +} + +internal fun JarManifestDiff.toDetailReport() = buildString { + if (diff.isNotEmpty()) { + appendLine() + diff + .drop(2) // Skip file name headers. + .forEach(::appendLine) + appendLine() + } +} diff --git a/reports/src/main/kotlin/com/jakewharton/diffuse/report/text/JarDiffTextReport.kt b/reports/src/main/kotlin/com/jakewharton/diffuse/report/text/JarDiffTextReport.kt index 4ebe34f3..5216c5a9 100644 --- a/reports/src/main/kotlin/com/jakewharton/diffuse/report/text/JarDiffTextReport.kt +++ b/reports/src/main/kotlin/com/jakewharton/diffuse/report/text/JarDiffTextReport.kt @@ -29,6 +29,13 @@ internal class JarDiffTextReport(private val jarDiff: JarDiff, private val summa appendLine("=================") appendLine(jarDiff.archive.toDetailReport()) } + if (jarDiff.manifest.changed) { + appendLine() + appendLine("======================") + appendLine("==== MANIFEST ====") + appendLine("======================") + appendLine(jarDiff.manifest.toDetailReport()) + } if (jarDiff.jars.changed) { appendLine() appendLine("=====================") diff --git a/reports/src/test/kotlin/com/jakewharton/diffuse/JarDiffTextReportTest.kt b/reports/src/test/kotlin/com/jakewharton/diffuse/JarDiffTextReportTest.kt new file mode 100644 index 00000000..91690540 --- /dev/null +++ b/reports/src/test/kotlin/com/jakewharton/diffuse/JarDiffTextReportTest.kt @@ -0,0 +1,63 @@ +package com.jakewharton.diffuse + +import assertk.assertThat +import assertk.assertions.contains +import assertk.assertions.doesNotContain +import com.jakewharton.diffuse.diff.JarDiff +import com.jakewharton.diffuse.diff.JarManifestDiff.Companion.MANIFEST_PATH +import com.jakewharton.diffuse.format.ApiMapping +import com.jakewharton.diffuse.format.Jar +import com.jakewharton.diffuse.format.Jar.Companion.toJar +import com.jakewharton.diffuse.io.Input.Companion.asInput +import java.io.ByteArrayOutputStream +import java.util.jar.JarOutputStream +import java.util.zip.ZipEntry +import okio.ByteString.Companion.toByteString +import org.junit.Test + +class JarDiffTextReportTest { + @Test + fun manifestChanged() { + val oldJar = jar("old.jar", "Manifest-Version: 1.0\nCreated-By: old") + val newJar = jar("new.jar", "Manifest-Version: 1.0\nCreated-By: new") + + val report = + JarDiff(oldJar, ApiMapping.EMPTY, newJar, ApiMapping.EMPTY).toTextReport(false).toString() + + assertThat(report) + .contains( + """ + |====================== + |==== MANIFEST ==== + |====================== + | + |@@ -1,2 +1,2 @@ + | Manifest-Version: 1.0 + |-Created-By: old + |+Created-By: new + """ + .trimMargin() + ) + } + + @Test + fun manifestNotChanged() { + val oldJar = jar("old.jar", "Manifest-Version: 1.0\nCreated-By: same") + val newJar = jar("new.jar", "Manifest-Version: 1.0\nCreated-By: same") + + val report = + JarDiff(oldJar, ApiMapping.EMPTY, newJar, ApiMapping.EMPTY).toTextReport(false).toString() + + assertThat(report).doesNotContain("==== MANIFEST ====") + } + + private fun jar(name: String, manifestContent: String): Jar { + val bytes = ByteArrayOutputStream() + JarOutputStream(bytes).use { jar -> + jar.putNextEntry(ZipEntry(MANIFEST_PATH)) + jar.write(manifestContent.toByteArray()) + jar.closeEntry() + } + return bytes.toByteArray().toByteString().asInput(name).toJar() + } +}