Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Metadataquoi??

 Metadataquoi??

Une nouvelle version de Kotlin vient de sortir! Yoohooo 🙌 C'est parti! C'est le moment de tout mettre à jour 🚀

Et c'est là que les ennuis commencent... Entre les erreurs Gradle, les dépendances transitives, les erreurs au runtime sur les vieilles versions d'Android, les cache miss, etc, difficile de s'y retrouver.

Dans cette session, nous regarderons les différentes garanties offertes par les différents composants de l'écosystème et les outils pour gérer de manière fine leur évolution.

Les metadatas n'auront plus de secrets pour vous!

mbonnin

April 11, 2025
Tweet

More Decks by mbonnin

Other Decks in Programming

Transcript

  1. Java features static public void doStuff() { var numbers =

    new ArrayList<Integer>(); numbers.add(1); numbers.add(2); numbers.add(3); numbers.removeFirst(); numbers.forEach(System.out :: println); } Java 10 (source) Java 21 (api) Java 8 (source) Java 7 (target)
  2. Main.java:1: error: cannot access Stuff import Stuff; ^ bad class

    file: lib.jar(Stuff) class file has wrong version 67.0, should be 52.0 Please remove or make sure it appears in the correct subdirectory of the classpath. Execution failed for task ':compileJava'. > Could not resolve all files for configuration ':compileClasspath'. > Could not resolve com.example:stuff:0.0.0. Required by: root project : > Dependency resolution is looking for a library compatible with JVM runtime version 8, but ‘com.example:stuff:0.0.0’ is only compatible with JVM runtime version 23 or newer. Exception in thread "main" java.lang.UnsupportedClassVersionError: Main has been compiled by a more recent version of the Java Runtime (class file version 67.0), this version of the Java Runtime only recognizes class file versions up to 52.0 Exception in thread "main" java.lang.NoSuchMethodError: java.util.ArrayList.removeFirst()Ljava/lang/Object; at Main.main(Main.java:9) Using with Java 8 💥
  3. java { toolchain { languageVersion = JavaLanguageVersion.of(8) } } java

    { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 }
  4. java { toolchain { languageVersion = JavaLanguageVersion.of(8) } } java

    { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } tasks.withType(JavaCompile :: class.java) { options.release = 8 } release = sourceCompatibility + targetCompatibility + apiCompatibility
  5. Kotlin 2.1.20 context(stream: PrintStream) fun doStuff() { val strings =

    mutableListOf( "one", "two", Uuid.random(), ) strings.removeFirst() strings.forEach(stream :: println) } kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_1_8) } } Kotlin 2.1 (source + target) Kotlin 2.0 (api) Java 21 (api) build.gradle.kts Kotlin 1.4 (source)
  6. Kotlin 2.1.20 context(stream: PrintStream) fun doStuff() { val strings =

    mutableListOf( "one", "two", Uuid.random(), ) strings.removeFirst() strings.forEach(stream :: println) } kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_1_8) freeCompilerArgs.add("-Xjdk-release=8") } } Kotlin 2.1 (source + target) Kotlin 2.0 (api) Java 21 (api) build.gradle.kts Kotlin 1.4 (source)
  7. Kotlin 2.1.20 context(stream: PrintStream) fun doStuff() { val strings =

    mutableListOf( "one", "two", Uuid.random(), ) strings.removeFirst() strings.forEach(stream :: println) } kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_1_8) freeCompilerArgs.add("-Xjdk-release=8") } } // This is also needed tasks.withType(JavaCompile :: class.java) { options.release = 8 } Kotlin 2.1 (source + target) Kotlin 2.0 (api) Java 21 (api) build.gradle.kts Kotlin 1.4 (source)
  8. e: file: // lib.jar!/META-INF/lib.kotlin_moduleModule was compiled with an incompatible version

    of Kotlin. The binary version of its metadata is 2.1.0, expected version is 1.9.0. Compiling with Kotlin 1.9.0 💥
  9. Metadata, kesako? $ javap -v -p Stuff.class Classfile Stuff Compiled

    from “stuff.kt" Last modified Apr 5, 2025; size 2214 bytes public final class StuffKt minor version: 0 major version: 52 Constant pool: #1 = Utf8 StuffKt #2 = Class #1 // StuffKt … { public static final void doStuff(java.io.PrintStream); descriptor: (Ljava/io/PrintStream;)V flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL Code: stack=3, locals=8, args_size=1 0: aload_0 1: ldc #9 / / String stream 3: invokestatic #15 / / Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/ Object;Ljava/lang/String;)V } ... InnerClasses: public static final #22= #27 of #21; // Companion=class kotlin/uuid/Uuid$Companion of class kotlin/uuid/Uuid RuntimeVisibleAnnotations: 0: #77(#78=[I#79,I#80,I#81],#82=I#79,#83=I#84,#85=[s#86],#87=[s#5,s#88,s#74,s#6,s#89]) kotlin.Metadata( mv=[2,1,0] k=2 xi=48 d1=["\u0000\u000e\n\u0000\n\u0002\u0010\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u000f\u0010\u0000\u001a\u00020\u0001R \u00020\u0002¢\u0006\u0002\u0010\u0003¨\u0006\u0004"] d2=["doStuff","","Ljava/io/PrintStream;","(Ljava/io/PrintStream;)V","kotlin-compatibility-crash-course"] ) context(stream: PrintStream) fun doStuff() { val strings = mutableListOf( "one", "two", Uuid.random(), ) strings.removeFirst() strings.forEach(stream :: println) }
  10. kotlin { compilerOptions { languageVersion.set(KotlinVersion.KOTLIN_1_9) apiVersion.set(KotlinVersion.KOTLIN_1_9) } coreLibrariesVersion = "1.9.0"

    } languageVersion & apiVersion •languageVersion: metadata version •apiVersion: stdlib version at compile time •coreLibrariesVersion: kotlin dependencies versions (stdlib, reflect, etc..)
  11. kotlin { compilerOptions { languageVersion.set(KotlinVersion.KOTLIN_2_0) apiVersion.set(KotlinVersion.KOTLIN_1_9) } coreLibrariesVersion = "1.9.0"

    } languageVersion & apiVersion kotlinc 1.9.0 understands! •kotlinc has n+1 best effort forward compatibility •kotlinc has forever backward compatibility •kotlin-stdlib has forever backward compatibility
  12. Android • art != openJDK • .dex != .class JVM

    javac rt.jar art D8 android.jar
  13. android { kotlinOptions { jvmTarget = "8" } compileOptions {

    // also needed targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 } } jvmTarget
  14. android { kotlinOptions { jvmTarget = "17" } compileOptions {

    // also needed targetCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17 } } Same dex code jvmTarget 🤷
  15. android { kotlinOptions { jvmTarget = "17" } compileOptions {

    // make Gradle happy targetCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17 } compileSdk = 36 defaultConfig { minSdk = 21 } } compileSdk and minSdk
  16. Gradle publishing { "formatVersion": "1.1", "component": { "group": "com.android.tools.build", "module":

    "gradle", "version": "8.9.1", "attributes": { "org.gradle.status": "release" } }, "createdBy": { "gradle": { "version": "8.11.1" } }, "variants": [ { "name": "apiElements", "attributes": { "org.gradle.category": "library", "org.gradle.dependency.bundling": "external", "org.gradle.jvm.environment": "standard-jvm", "org.gradle.jvm.version": 11, "org.gradle.libraryelements": "jar", "org.gradle.usage": "java-api", "org.jetbrains.kotlin.platform.type": "jvm" }, https://dl.google.com/android/maven2/com/android/tools/build/gradle/8.9.1/gradle-8.9.1.module kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) freeCompilerArgs.add("-Xjdk-release=11") } } // This is also needed to make Gradle happy tasks.withType(JavaCompile :: class.java) { options.release = 11 }
  17. Gradle embedded Kotlin plugins { id("org.jetbrains.kotlin.jvm").version("2.1.20") } kotlin { compilerOptions

    { // embeddedKotlinVersion = 2.0.21 KotlinVersion.fromVersion(embeddedKotlinVersion.substringBeforeLast(".")).let { languageVersion.set(it) apiVersion.set(it) } } coreLibrariesVersion = embeddedKotlinVersion }
  18. KMP

  19. •Use Java release. •kotlinc has n+1 forward compatibility. •Android jvmTarget

    doesn’t matter. •Isolate your Gradle tasks. •KMP is hard but getting better. Takeaways Use latest tooling with compatibility flags!
  20. Merci !!! https://github.com/GradleUp/compat-patrouille/ 🐶 https://kotlinlang.org/docs/compatibility-modes.html https://kotlinlang.org/docs/kotlin-evolution-principles.html https://jakewharton.com/kotlins-jdk-release-compatibility-flag/ https://jakewharton.com/gradle-toolchains-are-rarely-a-good-idea/ https://blog.alllex.me/posts/2023-11-03-liberal-library-tooling/ https://www.liutikas.net/2025/01/10/Conservative-Librarian.html

    https://source.android.com/docs/core/runtime/dex-format#dex-file-magic https://github.com/JetBrains/kotlin/blob/master/core/metadata/src/metadata.proto https://youtrack.jetbrains.com/issue/KT-68792/Bump-KLIB-ABI-version-in-2.1 https://youtrack.jetbrains.com/issue/KT-55808/Support-metadata-version-checks-for-klibs-in-the-compiler https://youtrack.jetbrains.com/issue/KT-42293/Native-provide-binary-compatibility-between-incremental-releases https://youtrack.jetbrains.com/issue/KT-71375/Prevent-Kotlins-removeFirst-and-removeLast-from-causing-crashes- on-Android-14-and-below-after-upgrading-to-Android-API-Level-35 https://youtrack.jetbrains.com/issue/KT-58998/Require-stdlib-version-consistent-with-api-version https://mbonnin.net/2021-11-17_kotlin-compatibility-quicksheet/ https://mbonnin.net/compatibility