|
| 1 | +# Automatic Metadata Resolution |
| 2 | + |
| 3 | +The goal of Nucleus is to make GraalVM native-image compilation **as transparent as possible**. In most cases, you should be able to run `packageGraalvmNative` and get a working binary without writing a single line of reflection configuration. To achieve this, Nucleus combines five complementary metadata sources that are resolved and merged automatically at build time. |
| 4 | + |
| 5 | +## Level 1 — Per-library conditional metadata |
| 6 | + |
| 7 | +The Nucleus Gradle plugin ships **28 per-library metadata files** covering Compose UI, Skiko, ktor, kotlinx.serialization, SQLite, Coil, JNA, FileKit, and many others. Each file declares a `matchPackages` condition — the metadata is only included if the corresponding library is actually present on your runtime classpath. |
| 8 | + |
| 9 | +This means libraries like ktor or SQLite JDBC **just work** in native image without any manual configuration. |
| 10 | + |
| 11 | +## Level 2 — Oracle Reachability Metadata Repository |
| 12 | + |
| 13 | +Nucleus automatically downloads the [Oracle GraalVM Reachability Metadata Repository](https://github.com/oracle/graalvm-reachability-metadata) and resolves metadata for all dependencies on your runtime classpath. This covers libraries that are not yet covered by Nucleus's own L1 metadata — SLF4J, Logback, and many others. The resolved metadata directories are passed to `native-image` via `-H:ConfigurationFileDirectories=`. |
| 14 | + |
| 15 | +This is enabled by default. To customize: |
| 16 | + |
| 17 | +```kotlin |
| 18 | +graalvm { |
| 19 | + metadataRepository { |
| 20 | + enabled = true // disable with false |
| 21 | + version = "0.10.6" // override repository version |
| 22 | + excludedModules.add("group:artifact") // skip specific dependencies |
| 23 | + moduleToConfigVersion.put( // pin a specific metadata version |
| 24 | + "io.ktor:ktor-client-core", |
| 25 | + "3.0.0", |
| 26 | + ) |
| 27 | + } |
| 28 | +} |
| 29 | +``` |
| 30 | + |
| 31 | +## Level 3 — Platform-specific metadata |
| 32 | + |
| 33 | +The Nucleus Gradle plugin ships pre-built platform-specific metadata for macOS, Windows, and Linux. These cover platform-specific AWT implementations (`sun.awt.windows.*`, `sun.lwawt.macosx.*`, `sun.awt.X11.*`), Java2D pipelines, font managers, and security providers. The plugin writes the correct platform metadata to the build directory at compile time — **no per-platform configuration needed in your build script**. |
| 34 | + |
| 35 | +## Level 4 — Static bytecode analysis |
| 36 | + |
| 37 | +Nucleus includes a **static bytecode analyzer** that scans all compiled classes on your runtime classpath at build time and automatically detects reflection, JNI, and resource requirements — without running the application. The analyzer detects: |
| 38 | + |
| 39 | +- **Native methods** and their parameter/return types (JNI metadata) |
| 40 | +- **`Class.forName()`** and **`MethodHandles.Lookup.findClass()`** calls (reflection metadata) |
| 41 | +- **`getResource()` / `getResourceAsStream()`** calls (resource metadata) |
| 42 | +- **JNI callback parameters** — classes passed to native code that call back into Java |
| 43 | +- **JNI superclass chains** — parent classes needed for field access from native code |
| 44 | +- **`@Serializable` classes** — automatically emits `Companion.serializer()` reflection entries |
| 45 | + |
| 46 | +This analysis runs transparently as part of the build (the `analyzeGraalvmStaticMetadata` task) and its output is passed to `native-image` alongside the other metadata levels. |
| 47 | + |
| 48 | +```mermaid |
| 49 | +flowchart LR |
| 50 | + jars["Runtime classpath JARs"] --> scan["Bytecode scanner"] |
| 51 | +
|
| 52 | + scan --> jni["JNI\nnative methods\n+ parameter types"] |
| 53 | + scan --> reflect["Reflection\nClass.forName()\nfindClass()"] |
| 54 | + scan --> res["Resources\ngetResource()\ngetResourceAsStream()"] |
| 55 | + scan --> serial["Serialization\n@Serializable\nCompanion.serializer()"] |
| 56 | + scan --> callback["JNI callbacks\n+ superclass chains"] |
| 57 | +
|
| 58 | + jni --> out["reachability-metadata.json"] |
| 59 | + reflect --> out |
| 60 | + res --> out |
| 61 | + serial --> out |
| 62 | + callback --> out |
| 63 | +
|
| 64 | + style scan fill:#0f3460,stroke:#16213e,color:#e0e0e0 |
| 65 | + style out fill:#533483,stroke:#16213e,color:#e0e0e0 |
| 66 | +``` |
| 67 | + |
| 68 | +## Level 5 — Generic cross-platform metadata |
| 69 | + |
| 70 | +The `graalvm-runtime` module ships a `reachability-metadata.json` inside its JAR that covers all cross-platform reflection entries: Compose Desktop, AWT/Swing, Skiko, security providers, font managers, and more (~300+ types). This metadata is **automatically picked up** by native-image from the classpath — no configuration needed. |
| 71 | + |
| 72 | +## How it all fits together |
| 73 | + |
| 74 | +When you run `packageGraalvmNative`, Nucleus automatically resolves all five metadata levels and passes them to `native-image`: |
| 75 | + |
| 76 | +```mermaid |
| 77 | +flowchart TB |
| 78 | + subgraph build ["packageGraalvmNative"] |
| 79 | + direction TB |
| 80 | + L1["L1 — Per-library metadata\n(28 conditional files in plugin)"] |
| 81 | + L2["L2 — Oracle Repository\n(auto-resolved for classpath deps)"] |
| 82 | + L3["L3 — Platform metadata\n(macOS / Windows / Linux)"] |
| 83 | + L4["L4 — Static bytecode analysis\n(scans compiled classes)"] |
| 84 | + L5["L5 — Generic metadata\n(graalvm-runtime JAR)"] |
| 85 | + end |
| 86 | +
|
| 87 | + L1 --> merge["Merge all metadata"] |
| 88 | + L2 --> merge |
| 89 | + L3 --> merge |
| 90 | + L4 --> merge |
| 91 | + L5 --> merge |
| 92 | +
|
| 93 | + merge --> NI["`**native-image** |
| 94 | + -H:ConfigurationFileDirectories=...`"] |
| 95 | + NI --> bin["Native binary"] |
| 96 | +
|
| 97 | + style build fill:#1a1a2e,stroke:#16213e,color:#e0e0e0 |
| 98 | + style merge fill:#0f3460,stroke:#16213e,color:#e0e0e0 |
| 99 | + style NI fill:#533483,stroke:#16213e,color:#e0e0e0 |
| 100 | + style bin fill:#e94560,stroke:#16213e,color:#e0e0e0 |
| 101 | +``` |
| 102 | + |
| 103 | +All of this happens transparently — no manual steps required. The result is that **most applications compile and run as native images without any manual reflection configuration**. |
| 104 | + |
| 105 | +## The tracing agent — a final safety net |
| 106 | + |
| 107 | +Even with five levels of automatic metadata, there can be edge cases that static analysis cannot catch: reflection driven by runtime values, dynamically loaded classes, or unusual library patterns. The tracing agent (`runWithNativeAgent`) remains available as a **final verification step**: |
| 108 | + |
| 109 | +```bash |
| 110 | +./gradlew runWithNativeAgent |
| 111 | +``` |
| 112 | + |
| 113 | +During the tracing run, navigate through every screen and feature of your application. The agent records all reflection, JNI, resource, and proxy accesses and **merges** the results into your existing configuration. Entries already covered by the five metadata levels are **automatically deduplicated** — the agent output stays minimal. |
| 114 | + |
| 115 | +In many cases, the agent will find nothing new — the automatic metadata already covers everything. But running it once before release is a good safety net, especially for applications with complex library dependencies. |
| 116 | + |
| 117 | +## Cleaning up manual metadata |
| 118 | + |
| 119 | +If you accumulated manual entries in your `reachability-metadata.json` that are now covered by the automatic metadata levels, you can clean them up: |
| 120 | + |
| 121 | +```bash |
| 122 | +./gradlew cleanupGraalvmMetadata |
| 123 | +``` |
| 124 | + |
| 125 | +This task compares your manual entries against the combined baseline (L1 + L2 + L3 + L4) and removes any that are already covered. It reports what was removed and what remains, so you can verify the cleanup is safe. |
0 commit comments