Introduction : Quand le compilateur dit « non »
Avez-vous déjà vu cette ligne rouge effrayante dans la console ? Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Si oui, vous n'êtes pas seul. Cette erreur a rassemblé des milliers de vues sur Stack Overflow et est devenue une véritable pierre d'achoppement pour les développeurs Kotlin. Mais que diriez-vous si je vous disais que derrière ce message se cache non seulement un bug, mais toute une histoire de l'évolution de la machine virtuelle Java ?
Dans cet article, nous ne montrerons pas simplement comment corriger l'erreur. Nous allons regarder sous le capot du compilateur Kotlin, comprendre ce qu'est une cible JVM, comment fonctionnent les fonctions inline et pourquoi le bytecode version 1.8 ne veut pas s'entendre avec la version 1.6. Attachez vos ceintures — ce sera intéressant. Et oui, nous écrirons en Kotlin, parce que ce n'est pas seulement à la mode, mais aussi diablement pratique.
Qu'est-ce que la cible JVM et pourquoi est-ce important ?
Pour commencer, clarifions les concepts de base. La JVM (Java Virtual Machine) est l'environnement dans lequel votre code Kotlin s'exécute. Chaque version de la JVM comprend un bytecode d'une version spécifique. Par exemple :
- Cible JVM 1.6 — bytecode compatible avec Java 6. C'est une version ancienne mais stable.
- Cible JVM 1.8 — bytecode pour Java 8. Ajoute le support des lambdas, Stream API, des nouvelles annotations et d'autres fonctionnalités.
Lorsque vous écrivez kotlin { jvmTarget = "1.8" } dans build.gradle.kts, vous dites au compilateur : « Génère un bytecode que la JVM 1.8 comprendra ». Et si vous spécifiez "1.6", le bytecode sera compatible avec des versions plus anciennes.
Le problème survient lorsque différentes parties de votre projet (ou bibliothèques) sont compilées avec des cibles JVM différentes. Le compilateur Kotlin voit l'incohérence et panique. Cela concerne particulièrement les fonctions inline, qui sont un outil d'optimisation puissant en Kotlin.
Fonctions inline : comment fonctionnent-elles sous le capot ?
En Kotlin, le mot-clé inline force le compilateur à intégrer le code de la fonction directement à l'endroit de l'appel. Cela diffère des fonctions normales, où un appel se fait via la pile. Les fonctions inline accélèrent l'exécution (surtout avec les lambdas), mais créent un problème : le bytecode de la fonction est copié à chaque appel.
Imaginez que vous ayez une bibliothèque compilée avec la cible JVM 1.8. À l'intérieur, une fonction inline utilise invokedynamic (une instruction apparue dans Java 7/8 pour les lambdas). Vous liez cette bibliothèque à un projet avec une cible JVM 1.6. Le compilateur tente d'intégrer le bytecode avec invokedynamic dans un contexte où cette instruction n'existe pas. Et il obtient une erreur.
// Exemple de fonction inline dans une bibliothèque (compilée avec target 1.8)inline fun performAction(action: () -> Unit) { println("Avant") action() println("Après")}
// Votre code (compilé avec target 1.6)fun main() { performAction { println("Bonjour !") } // ERREUR : Cannot inline bytecode...}Le compilateur crie : « Hé, je ne peux pas copier ce bytecode, il utilise des fonctionnalités de la JVM 1.8, alors que ma cible est 1.6 ! »
Pourquoi cela se produit-il dans des projets réels ?
La raison la plus fréquente est un conflit de versions dans les dépendances Gradle. Par exemple :
- Vous utilisez la bibliothèque Kotlin coroutines (qui est compilée avec la cible 1.8)
- Votre projet est configuré avec la cible 1.6 (peut-être à cause d'un modèle obsolète ou d'exigences de compatibilité)
- Lors de la compilation, Kotlin tente d'intégrer le bytecode des coroutines et rencontre une incompatibilité
Une autre situation est l'appel direct de fonctions inline depuis différents modules. Si le module A est compilé avec la cible 1.8 et le module B avec la cible 1.6, et que vous appelez une fonction inline de A dans B, l'erreur est garantie.
À quoi cela ressemble-t-il en pratique : analyse d'un exemple
Simulons la situation. Créons un projet simple en Kotlin avec Gradle.
// build.gr