Nucleus
Performance & native

Accès natif — la metadata de réflexion résolue automatiquement

Nucleus injecte automatiquement les reflect-config, resource-config et metadata JNI pour chaque module Nucleus et la plupart des libs Kotlin idiomatiques — le build GraalVM marche sans écrire de JSON.

GraalVM Native Image a une taxe célèbre : chaque Class.forName réflectif, chaque glob getResource, chaque appel JNI doit être déclaré en JSON au build. L'hypothèse du monde fermé rend le binaire minuscule et le cold start instantané — mais écrire la metadata de reachability à la main est le genre de travail qui mange silencieusement une semaine. Nucleus le fait automatiquement.

TL;DR

  • Nucleus livre cinq couches de metadata automatique — config par lib, le Reachability Repository d'Oracle, des entrées spécifiques à la plateforme, l'analyse statique de bytecode, et une baseline générique. Elles se mergent automatiquement au build.
  • Pour chaque module runtime Nucleus, le reflect-config / resource-config / jni-config correspondant est déjà dans le JAR.
  • Résultat : ./gradlew packageGraalvmNative produit un binaire qui marche sans qu'on écrive une ligne de metadata.
  • Un agent de tracing (runWithNativeAgent) est dispo comme filet de sécurité final.

Le problème que ça résout

Si on a déjà GraalVM-buildé une app Compose Desktop à la main, on connaît la boucle : build, run, crash sur une entrée réflexion manquante, ajouter du JSON, rebuilder. Compose seul demande des centaines d'entrées. Ajoute Skiko, ktor, kotlinx.serialization, SQLite, Coil — on maintient un reachability-metadata.json de plusieurs milliers de lignes qui casse à chaque bump de dépendance.

Nucleus remplace cette boucle par de l'automatisation au build.

Cinq niveaux de metadata

NiveauCouvreMécanique
L1 — Metadata conditionnelle par lib28 fichiers curés : Compose UI, Skiko, ktor, kotlinx.serialization, SQLite, Coil, JNA, FileKit, …Livrés dans le plugin. Chaque fichier déclare matchPackages — inclus seulement si la lib est sur le classpath.
L2 — Oracle Reachability Metadata RepositorySLF4J, Logback, des centaines d'autres libs communautairesRésolu automatiquement depuis le repository officiel pour chaque dépendance runtime.
L3 — Metadata plateformesun.awt.windows.*, sun.lwawt.macosx.*, sun.awt.X11.*, pipelines Java2D, gestionnaires de polices, security providersÉcrit dans le build dir au compile time pour l'OS cible.
L4 — Analyse statique de bytecodeClass.forName, MethodHandles.Lookup.findClass, getResource[AsStream], méthodes natives JNI + chaînes de paramètres, companions @SerializableLa tâche analyzeGraalvmStaticMetadata scanne chaque classe compilée du classpath runtime.
L5 — Metadata cross-platform génériqueCompose Desktop, AWT/Swing, Skiko, security providers, gestionnaires de polices (~300+ types)Livré dans nucleus.graalvm-runtime. Récupéré depuis le classpath automatiquement.

Les cinq sont mergés et passés à native-image via -H:ConfigurationFileDirectories=.

Inclusion automatique des ressources

Le JAR graalvm-runtime enregistre quatre patterns larges :

PatternCouvre
.*\.(svg|ttf|otf)Toutes les SVG et polices du classpath — Jewel, icônes IntelliJ Platform, ressources Compose, les tiennes
composeResources/.*Chaque ressource Compose Multiplatform chargée via Res.*
nucleus/native/.*Chaque bibliothèque partagée JNI Nucleus
META-INF/services/.*Chaque descripteur ServiceLoader (moteurs ktor, fetchers coil, providers SLF4J, …)

C'est la raison principale pour laquelle "les icônes s'affichent blanches en image native" n'arrive pas sous Nucleus.

Compromis sur la taille du binaire

Le glob est large exprès. Si on dépend de la lib d'icônes IntelliJ Platform et que la taille du binaire est critique, surcharge avec un resource-config.json plus serré.

Substitutions de polices

graalvm-runtime livre des substitutions @TargetClass qui corrigent les bords AWT-sur-image-native :

  • FontCreateFontSubstitution — bufferise Font.createFont(int, InputStream) vers un fichier temp sous Windows.
  • Win32FontManagerSubstitution — réimplémentation pure-Java de Win32FontManager.getFontPath(), évite InternalError: platform encoding not initialized.
  • FcFontManagerSubstitution — même fix sur le chemin Fontconfig de Linux.

Aucune config nécessaire — le compilateur native-image les prend en compte.

L'agent de tracing — filet de sécurité final

Même avec cinq niveaux, des cas-bords existent : réflexion pilotée par des valeurs runtime, classes chargées dynamiquement, patterns de libs inhabituels. Lance l'agent une fois avant chaque release :

./gradlew runWithNativeAgent

Parcours chaque écran et chaque feature. L'agent enregistre les accès réflexion / JNI / ressources / proxies, puis merge dédupliqué dans la config. La plupart du temps il ne trouve rien — c'est le but.

Nettoyer la metadata manuelle

Si on a accumulé des entrées manuelles que les couches automatiques couvrent maintenant :

./gradlew cleanupGraalvmMetadata

La tâche diff les entrées manuelles contre L1+L2+L3+L4 et retire tout ce qui est redondant, en reportant exactement ce qui a changé.

Aller plus loin — écris le propre code natif en Kotlin

Si on doit appeler une API plateforme qu'aucun module runtime Nucleus ne couvre (CoreGraphics, IOKit, Win32, GTK4, …), NucleusNativeAccess est un plugin séparé qui laisse écrire le côté natif en Kotlin/Native et génère le bridge FFM automatiquement. Le plugin émet reflect-config.json, resource-config.json et reachability-metadata.json pour les proxies générés — récupérés transparente­ment par le pipeline GraalVM Nucleus.

L'ID du plugin est dev.nucleusframeworknativeaccess, versionné indépendamment de Nucleus. Voir son repository pour la table de mapping complète et des exemples.

Quand la metadata auto ne suffit pas

Certaines catégories de libs rendent GraalVM impraticable, peu importe la metadata :

  • Gros utilisateurs JNA (proxies d'appels de fonctions dynamiques)
  • Moteurs de recherche full-text (Lucene, client Elasticsearch)
  • Runtimes de scripting embarqués (Groovy, JRuby, Nashorn)
  • Annotation-processing runtime (Spring)
  • OSGi / classloaders custom
  • Génération de bytecode runtime (ByteBuddy, cglib, mocking ASM)

Si on dépend de l'un d'eux, livre plutôt le build AOT cache. Pour tout le reste — Compose, ktor, kotlinx.serialization, Coil, SQLite, Jewel, SLF4J, Kotlin idiomatique — Nucleus gère.

Référence

Voir GraalVM > Metadata automatique pour le graphe de tâches complet et la référence des tâches Gradle.