Skip to content

Commit

Permalink
KlibDump API imporovements (Kotlin#253)
Browse files Browse the repository at this point in the history
- returning the target object (file or appendable) from saveTo
- merge and KlibDump.from a char sequence
- new "replace" method as a shortcut for remove + merge
  • Loading branch information
ilya-g authored Jul 10, 2024
1 parent 05741e7 commit daebfc1
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 15 deletions.
9 changes: 7 additions & 2 deletions api/binary-compatibility-validator.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down
72 changes: 67 additions & 5 deletions src/main/kotlin/api/klib/KlibDump.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"))
* ```
*/
Expand Down Expand Up @@ -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
Expand All @@ -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.
*
Expand All @@ -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.
*
Expand Down Expand Up @@ -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 <A : Appendable> saveTo(to: A): A {
merger.dump(to)
return to
}

public companion object {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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) } }
113 changes: 105 additions & 8 deletions src/test/kotlin/samples/KlibDumpSamples.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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: <org.example:bcv-klib-test>
// 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 <get-value>(): kotlin/Int // org.example/ShardedClass.value.<get-value>|<get-value>(){}[0]
constructor <init>(kotlin/Int) // org.example/ShardedClass.<init>|<init>(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: <org.example:bcv-klib-test>
// 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 <get-value>(): kotlin/Int // org.example/ShardedClass.value.<get-value>|<get-value>(){}[0]
constructor <init>(kotlin/Int) // org.example/ShardedClass.<init>|<init>(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: <org.example:bcv-klib-test>
// 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 <get-value>(): kotlin/Int // org.example/ShardedClass.value.<get-value>|<get-value>(){}[0]
constructor <init>(kotlin/Int) // org.example/ShardedClass.<init>|<init>(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: <org.example:bcv-klib-test>
final class org.example/ShardedClass { // org.example/ShardedClass|null[0]
constructor <init>(kotlin/Int) // org.example/ShardedClass.<init>|<init>(kotlin.Int){}[0]
final val value // org.example/ShardedClass.value|{}value[0]
final fun <get-value>(): kotlin/Int // org.example/ShardedClass.value.<get-value>|<get-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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down

0 comments on commit daebfc1

Please sign in to comment.