diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 0c836a5d..39b16068 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -141,16 +141,21 @@ public final class kotlinx/validation/api/klib/KlibDump { public final fun copy ()Lkotlinx/validation/api/klib/KlibDump; public final fun getTargets ()Ljava/util/Set; public final fun merge (Ljava/io/File;Ljava/lang/String;)V + public final fun merge (Ljava/lang/CharSequence;Ljava/lang/String;)V public final fun merge (Lkotlinx/validation/api/klib/KlibDump;)V public static synthetic fun merge$default (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)V + public static synthetic fun merge$default (Lkotlinx/validation/api/klib/KlibDump;Ljava/lang/CharSequence;Ljava/lang/String;ILjava/lang/Object;)V public final fun remove (Ljava/lang/Iterable;)V + public final fun replace (Lkotlinx/validation/api/klib/KlibDump;)V public final fun retain (Ljava/lang/Iterable;)V - public final fun saveTo (Ljava/lang/Appendable;)V + public final fun saveTo (Ljava/lang/Appendable;)Ljava/lang/Appendable; } public final class kotlinx/validation/api/klib/KlibDump$Companion { public final fun from (Ljava/io/File;Ljava/lang/String;)Lkotlinx/validation/api/klib/KlibDump; + public final fun from (Ljava/lang/CharSequence;Ljava/lang/String;)Lkotlinx/validation/api/klib/KlibDump; public static synthetic fun from$default (Lkotlinx/validation/api/klib/KlibDump$Companion;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/validation/api/klib/KlibDump; + public static synthetic fun from$default (Lkotlinx/validation/api/klib/KlibDump$Companion;Ljava/lang/CharSequence;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/validation/api/klib/KlibDump; public final fun fromKlib (Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KlibDumpFilters;)Lkotlinx/validation/api/klib/KlibDump; public static synthetic fun fromKlib$default (Lkotlinx/validation/api/klib/KlibDump$Companion;Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KlibDumpFilters;ILjava/lang/Object;)Lkotlinx/validation/api/klib/KlibDump; } @@ -186,7 +191,7 @@ public final class kotlinx/validation/api/klib/KlibDumpKt { public static synthetic fun inferAbi$default (Lkotlinx/validation/api/klib/KlibTarget;Ljava/lang/Iterable;Lkotlinx/validation/api/klib/KlibDump;ILjava/lang/Object;)Lkotlinx/validation/api/klib/KlibDump; public static final fun mergeFromKlib (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KlibDumpFilters;)V public static synthetic fun mergeFromKlib$default (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;Ljava/lang/String;Lkotlinx/validation/api/klib/KlibDumpFilters;ILjava/lang/Object;)V - public static final fun saveTo (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;)V + public static final fun saveTo (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;)Ljava/io/File; } public final class kotlinx/validation/api/klib/KlibSignatureVersion : java/io/Serializable { diff --git a/src/main/kotlin/api/klib/KlibDump.kt b/src/main/kotlin/api/klib/KlibDump.kt index 2b8f35c4..740682ac 100644 --- a/src/main/kotlin/api/klib/KlibDump.kt +++ b/src/main/kotlin/api/klib/KlibDump.kt @@ -6,7 +6,6 @@ package kotlinx.validation.api.klib import kotlinx.validation.ExperimentalBCVApi -import org.jetbrains.kotlin.ir.backend.js.MainModule import java.io.File import java.io.FileNotFoundException @@ -38,8 +37,7 @@ import java.io.FileNotFoundException * ```kotlin * val mergedDump = KlibDump.from(File("/path/to/merged.klib.api")) * val newTargetDump = KlibDump.fromKlib(File("/path/to/library-linuxX64.klib")) - * mergedDump.remove(newTargetDump.targets) - * mergedDump.merge(newTargetDump) + * mergedDump.replace(newTargetDump) * mergedDump.saveTo(File("/path/to/merged.klib.api")) * ``` */ @@ -72,6 +70,7 @@ public class KlibDump { * @throws IllegalArgumentException if [dumpFile] contains multiple targets * and [configurableTargetName] is not null. * @throws IllegalArgumentException if [dumpFile] is not a file. + * @throws IllegalArgumentException if [dumpFile] is empty. * @throws FileNotFoundException if [dumpFile] does not exist. * * @sample samples.KlibDumpSamples.mergeDumps @@ -82,6 +81,29 @@ public class KlibDump { merger.merge(dumpFile, configurableTargetName) } + /** + * Reads a textual KLib dump from the [dump] char sequence and merges it into this dump. + * + * If a dump contains only a single target, it's possible to specify a custom configurable target name. + * Please refer to [KlibTarget.configurableName] for more details on the meaning of that name. + * + * By default, [configurableTargetName] is null and information about a target will be taken directly from + * the loaded dump. + * + * It's an error to specify non-null [configurableTargetName] for a dump containing multiple targets. + * It's also an error to merge dumps having some targets in common. + * + * @throws IllegalArgumentException if this dump and the provided [dump] shares same targets. + * @throws IllegalArgumentException if the provided [dump] contains multiple targets + * and [configurableTargetName] is not null. + * @throws IllegalArgumentException if the provided [dump] is empty. + * + * @sample samples.KlibDumpSamples.updateMergedDump + */ + public fun merge(dump: CharSequence, configurableTargetName: String? = null) { + merger.merge(dump.lineSequence().iterator(), configurableTargetName) + } + /** * Merges [other] dump with this one. * @@ -101,6 +123,19 @@ public class KlibDump { merger.merge(other.merger) } + /** + * Removes the targets from this dump that are contained in the [other] targets set and all their declarations. + * Then merges the [other] dump with this one. + * + * The operation does not modify [other]. + * + * @sample samples.KlibDumpSamples.updateMergedDump + */ + public fun replace(other: KlibDump) { + remove(other.targets) + merge(other) + } + /** * Removes all declarations that do not belong to specified targets and removes these targets from the dump. * @@ -134,10 +169,13 @@ public class KlibDump { /** * Serializes the dump and writes it to [to]. * + * @return the target [to] where the dump was written. + * * @sample samples.KlibDumpSamples.mergeDumps */ - public fun saveTo(to: Appendable) { + public fun saveTo(to: A): A { merger.dump(to) + return to } public companion object { @@ -166,6 +204,28 @@ public class KlibDump { return KlibDump().apply { merge(dumpFile, configurableTargetName) } } + /** + * Reads a dump from a textual form. + * + * If a dump contains only a single target, it's possible to specify a custom configurable target name. + * Please refer to [KlibTarget.configurableName] for more details on the meaning of that name. + * + * By default, [configurableTargetName] is null and information about a target will be taken directly from + * the loaded dump. + * + * It's an error to specify non-null [configurableTargetName] for a dump containing multiple targets. + * + * @throws IllegalArgumentException if this dump and the provided [dump] shares same targets. + * @throws IllegalArgumentException if the provided [dump] contains multiple targets + * and [configurableTargetName] is not null. + * @throws IllegalArgumentException if the provided [dump] is empty. + * + * @sample samples.KlibDumpSamples.updateMergedDump + */ + public fun from(dump: CharSequence, configurableTargetName: String? = null): KlibDump { + return KlibDump().apply { merge(dump, configurableTargetName) } + } + /** * Dumps a public ABI of a klib represented by [klibFile] using [filters] * and returns a [KlibDump] representing it. @@ -278,6 +338,8 @@ public fun KlibDump.mergeFromKlib( /** * Serializes the dump and writes it to [file]. + * + * @return the target [file]. */ @ExperimentalBCVApi -public fun KlibDump.saveTo(file: File): Unit = file.bufferedWriter().use { saveTo(it) } +public fun KlibDump.saveTo(file: File): File = file.apply { bufferedWriter().use { saveTo(it) } } diff --git a/src/test/kotlin/samples/KlibDumpSamples.kt b/src/test/kotlin/samples/KlibDumpSamples.kt index 0f5f48fd..42acd153 100644 --- a/src/test/kotlin/samples/KlibDumpSamples.kt +++ b/src/test/kotlin/samples/KlibDumpSamples.kt @@ -20,7 +20,7 @@ class KlibDumpSamples { @Rule var tempFolder = TemporaryFolder() - fun createDumpWithContent(content: String): File { + fun createDumpFileWithContent(content: String): File { val file = tempFolder.newFile() file.writer().use { it.write(content) @@ -31,7 +31,7 @@ class KlibDumpSamples { @OptIn(ExperimentalBCVApi::class) @Test fun mergeDumps() { - val linuxX64Dump = createDumpWithContent(""" + val linuxX64Dump = createDumpFileWithContent(""" // Rendering settings: // - Signature version: 2 // - Show manifest properties: false @@ -51,7 +51,7 @@ class KlibDumpSamples { final fun org.example/ShardedClass(kotlin/Int, kotlin/Float, kotlin/Long): org.example/ShardedClass // org.example/ShardedClass|ShardedClass(kotlin.Int;kotlin.Float;kotlin.Long){}[0] """.trimIndent()) - val linuxArm64Dump = createDumpWithContent(""" + val linuxArm64Dump = createDumpFileWithContent(""" // Rendering settings: // - Signature version: 2 // - Show manifest properties: false @@ -103,7 +103,7 @@ class KlibDumpSamples { @OptIn(ExperimentalBCVApi::class) @Test fun mergeDumpObjects() { - val linuxX64Dump = createDumpWithContent(""" + val linuxX64Dump = createDumpFileWithContent(""" // Rendering settings: // - Signature version: 2 // - Show manifest properties: false @@ -123,7 +123,7 @@ class KlibDumpSamples { final fun org.example/ShardedClass(kotlin/Int, kotlin/Float, kotlin/Long): org.example/ShardedClass // org.example/ShardedClass|ShardedClass(kotlin.Int;kotlin.Float;kotlin.Long){}[0] """.trimIndent()) - val linuxArm64Dump = createDumpWithContent(""" + val linuxArm64Dump = createDumpFileWithContent(""" // Rendering settings: // - Signature version: 2 // - Show manifest properties: false @@ -193,11 +193,108 @@ class KlibDumpSamples { """.trimIndent(), filteredDumpContent) } + @OptIn(ExperimentalBCVApi::class) + @Test + fun updateMergedDump() { + val linuxX64Dump = """ + // Rendering settings: + // - Signature version: 2 + // - Show manifest properties: false + // - Show declarations: true + + // Library unique name: + // Platform: NATIVE + // Native targets: linux_x64 + // Compiler version: 1.9.22 + // ABI version: 1.8.0 + final class org.example/ShardedClass { // org.example/ShardedClass|null[0] + final val value // org.example/ShardedClass.value|{}value[0] + final fun (): kotlin/Int // org.example/ShardedClass.value.|(){}[0] + constructor (kotlin/Int) // org.example/ShardedClass.|(kotlin.Int){}[0] + final fun add(kotlin/Int): kotlin/Int // org.example/ShardedClass.add|add(kotlin.Int){}[0] + } + final fun org.example/ShardedClass(kotlin/Int, kotlin/Float, kotlin/Long): org.example/ShardedClass // org.example/ShardedClass|ShardedClass(kotlin.Int;kotlin.Float;kotlin.Long){}[0] + """.trimIndent() + + val linuxArm64Dump = """ + // Rendering settings: + // - Signature version: 2 + // - Show manifest properties: false + // - Show declarations: true + + // Library unique name: + // Platform: NATIVE + // Native targets: linux_arm64 + // Compiler version: 1.9.22 + // ABI version: 1.8.0 + final class org.example/ShardedClass { // org.example/ShardedClass|null[0] + final val value // org.example/ShardedClass.value|{}value[0] + final fun (): kotlin/Int // org.example/ShardedClass.value.|(){}[0] + constructor (kotlin/Int) // org.example/ShardedClass.|(kotlin.Int){}[0] + final fun add(kotlin/Int): kotlin/Int // org.example/ShardedClass.add|add(kotlin.Int){}[0] + } + """.trimIndent() + + val mergedDump = KlibDump() + mergedDump.merge(KlibDump.from(linuxArm64Dump)) + mergedDump.merge(KlibDump.from(linuxX64Dump, configurableTargetName = "linuxX86_64")) + + val newLinuxX64Dump = """ + // Rendering settings: + // - Signature version: 2 + // - Show manifest properties: false + // - Show declarations: true + + // Library unique name: + // Platform: NATIVE + // Native targets: linux_x64 + // Compiler version: 1.9.22 + // ABI version: 1.8.0 + final class org.example/ShardedClass { // org.example/ShardedClass|null[0] + final val value // org.example/ShardedClass.value|{}value[0] + final fun (): kotlin/Int // org.example/ShardedClass.value.|(){}[0] + constructor (kotlin/Int) // org.example/ShardedClass.|(kotlin.Int){}[0] + final fun store(kotlin/Int): kotlin/Int // org.example/ShardedClass.store|store(kotlin.Long){}[0] + } + final fun org.example/ShardedClass(kotlin/Int, kotlin/Float, kotlin/Long): org.example/ShardedClass // org.example/ShardedClass|ShardedClass(kotlin.Int;kotlin.Float;kotlin.Long){}[0] + """.trimIndent() + + mergedDump.replace(KlibDump.from(newLinuxX64Dump, configurableTargetName = "linuxX86_64")) + + val mergedDumpContent = mergedDump.saveTo(StringBuilder()).toString() + assertEquals(""" + // Klib ABI Dump + // Targets: [linuxArm64, linuxX64.linuxX86_64] + // Rendering settings: + // - Signature version: 2 + // - Show manifest properties: false + // - Show declarations: true + + // Library unique name: + final class org.example/ShardedClass { // org.example/ShardedClass|null[0] + constructor (kotlin/Int) // org.example/ShardedClass.|(kotlin.Int){}[0] + + final val value // org.example/ShardedClass.value|{}value[0] + final fun (): kotlin/Int // org.example/ShardedClass.value.|(){}[0] + + // Targets: [linuxArm64] + final fun add(kotlin/Int): kotlin/Int // org.example/ShardedClass.add|add(kotlin.Int){}[0] + + // Targets: [linuxX64.linuxX86_64] + final fun store(kotlin/Int): kotlin/Int // org.example/ShardedClass.store|store(kotlin.Long){}[0] + } + + // Targets: [linuxX64.linuxX86_64] + final fun org.example/ShardedClass(kotlin/Int, kotlin/Float, kotlin/Long): org.example/ShardedClass // org.example/ShardedClass|ShardedClass(kotlin.Int;kotlin.Float;kotlin.Long){}[0] + + """.trimIndent(), mergedDumpContent) + } + @OptIn(ExperimentalBCVApi::class) @Test fun extractTargets() { // Oh no, we're running on Windows and Apple targets are unsupported, let's filter it out! - val mergedDump = createDumpWithContent(""" + val mergedDump = createDumpFileWithContent(""" // Klib ABI Dump // Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64] // Rendering settings: @@ -243,7 +340,7 @@ class KlibDumpSamples { // We want to get a dump for iosArm64, but our host compiler doesn't support it. val unsupportedTarget = KlibTarget.parse("iosArm64") // Thankfully, we have an old merged dump ... - val oldMergedDump = createDumpWithContent(""" + val oldMergedDump = createDumpFileWithContent(""" // Klib ABI Dump // Targets: [iosArm64, linuxArm64] // Rendering settings: @@ -260,7 +357,7 @@ class KlibDumpSamples { """.trimIndent()) // ... and a new dump for linuxArm64 - val linuxDump = createDumpWithContent(""" + val linuxDump = createDumpFileWithContent(""" // Klib ABI Dump // Targets: [linuxArm64] // Rendering settings: