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() + } +}