ajalt / mordant

Multiplatform text styling for Kotlin command-line applications
https://ajalt.github.io/mordant/
Apache License 2.0
972 stars 34 forks source link

R8 issue with JDK <22 #233

Open TWiStErRob opened 1 day ago

TWiStErRob commented 1 day ago

I'm using an R8 setup very similar to the test module here.

After updating Clikt to 5.0.0 I started getting an error like this (click to expand) ``` > Task :ghlint-cli:r8Jar Error: Missing class java.lang.foreign.AddressLayout (referenced from: java.lang.foreign.AddressLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.POINTER and 6 other contexts) Missing class java.lang.foreign.Arena (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.allocateInt(java.lang.foreign.Arena) and 7 other contexts) Missing class java.lang.foreign.FunctionDescriptor (referenced from: java.lang.invoke.MethodHandle com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.handle$lambda$3$lambda$2$lambda$0(com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder, java.lang.foreign.MemoryLayout, java.lang.foreign.MemoryLayout[], java.lang.foreign.MemorySegment)) Missing class java.lang.foreign.Linker$Option (referenced from: java.lang.invoke.MethodHandle com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.handle$lambda$3$lambda$2$lambda$0(com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder, java.lang.foreign.MemoryLayout, java.lang.foreign.MemoryLayout[], java.lang.foreign.MemorySegment)) Missing class java.lang.foreign.Linker (referenced from: java.lang.foreign.Linker com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.linker and 6 other contexts) Missing class java.lang.foreign.MemoryLayout$PathElement (referenced from: long com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.byteOffset(java.lang.foreign.MemoryLayout, java.lang.String) and 1 other context) Missing class java.lang.foreign.MemoryLayout (referenced from: java.lang.foreign.MemoryLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt$shortField$$inlined$scalarField$default$1.$layout and 46 other contexts) Missing class java.lang.foreign.MemorySegment (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.LinuxCLibrary$winsize.segment and 61 other contexts) Missing class java.lang.foreign.StructLayout (referenced from: java.lang.foreign.MemoryLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.StructLayout.getLayout()) Missing class java.lang.foreign.SymbolLookup (referenced from: java.lang.foreign.SymbolLookup com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.lookup and 6 other contexts) Missing class java.lang.foreign.ValueLayout$OfByte (referenced from: java.lang.foreign.ValueLayout$OfByte com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.BYTE and 2 other contexts) Missing class java.lang.foreign.ValueLayout$OfChar (referenced from: java.lang.foreign.ValueLayout$OfChar com.github.ajalt.mordant.terminal.terminalinterface.ffm.WinLayouts.WCHAR and 1 other context) Missing class java.lang.foreign.ValueLayout$OfInt (referenced from: java.lang.foreign.ValueLayout$OfInt com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.INT and 4 other contexts) Missing class java.lang.foreign.ValueLayout$OfLong (referenced from: java.lang.foreign.ValueLayout$OfLong com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.LONG and 2 other contexts) Missing class java.lang.foreign.ValueLayout$OfShort (referenced from: java.lang.foreign.ValueLayout$OfShort com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.SHORT and 4 other contexts) Missing class java.lang.foreign.ValueLayout (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.allocateInt(java.lang.foreign.Arena) and 2 other contexts) Compilation failed Exception in thread "main" java.lang.RuntimeException: com.android.tools.r8.CompilationFailedException: Compilation failed to complete at com.android.tools.r8.internal.to.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:131) at com.android.tools.r8.R8.main(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:5) Caused by: com.android.tools.r8.CompilationFailedException: Compilation failed to complete at Version.fakeStackEntry(Version_8.5.35.java:0) at com.android.tools.r8.T.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:5) at com.android.tools.r8.internal.to.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:82) at com.android.tools.r8.internal.to.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:32) at com.android.tools.r8.internal.to.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:31) at com.android.tools.r8.internal.to.c(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:1) at com.android.tools.r8.R8.b(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:35) at com.android.tools.r8.R8.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:1548) at com.android.tools.r8.internal.to.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:118) ... 1 more Caused by: com.android.tools.r8.internal.g: Missing class java.lang.foreign.AddressLayout (referenced from: java.lang.foreign.AddressLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.POINTER and 6 other contexts) Missing class java.lang.foreign.Arena (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.allocateInt(java.lang.foreign.Arena) and 7 other contexts) Missing class java.lang.foreign.FunctionDescriptor (referenced from: java.lang.invoke.MethodHandle com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.handle$lambda$3$lambda$2$lambda$0(com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder, java.lang.foreign.MemoryLayout, java.lang.foreign.MemoryLayout[], java.lang.foreign.MemorySegment)) Missing class java.lang.foreign.Linker$Option (referenced from: java.lang.invoke.MethodHandle com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.handle$lambda$3$lambda$2$lambda$0(com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder, java.lang.foreign.MemoryLayout, java.lang.foreign.MemoryLayout[], java.lang.foreign.MemorySegment)) Missing class java.lang.foreign.Linker (referenced from: java.lang.foreign.Linker com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.linker and 6 other contexts) Missing class java.lang.foreign.MemoryLayout$PathElement (referenced from: long com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.byteOffset(java.lang.foreign.MemoryLayout, java.lang.String) and 1 other context) Missing class java.lang.foreign.MemoryLayout (referenced from: java.lang.foreign.MemoryLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt$shortField$$inlined$scalarField$default$1.$layout and 46 other contexts) Missing class java.lang.foreign.MemorySegment (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.LinuxCLibrary$winsize.segment and 61 other contexts) Missing class java.lang.foreign.StructLayout (referenced from: java.lang.foreign.MemoryLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.StructLayout.getLayout()) Missing class java.lang.foreign.SymbolLookup (referenced from: java.lang.foreign.SymbolLookup com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.lookup and 6 other contexts) Missing class java.lang.foreign.ValueLayout$OfByte (referenced from: java.lang.foreign.ValueLayout$OfByte com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.BYTE and 2 other contexts) Missing class java.lang.foreign.ValueLayout$OfChar (referenced from: java.lang.foreign.ValueLayout$OfChar com.github.ajalt.mordant.terminal.terminalinterface.ffm.WinLayouts.WCHAR and 1 other context) Missing class java.lang.foreign.ValueLayout$OfInt (referenced from: java.lang.foreign.ValueLayout$OfInt com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.INT and 4 other contexts) Missing class java.lang.foreign.ValueLayout$OfLong (referenced from: java.lang.foreign.ValueLayout$OfLong com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.LONG and 2 other contexts) Missing class java.lang.foreign.ValueLayout$OfShort (referenced from: java.lang.foreign.ValueLayout$OfShort com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.SHORT and 4 other contexts) Missing class java.lang.foreign.ValueLayout (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.allocateInt(java.lang.foreign.Arena) and 2 other contexts) at com.android.tools.r8.internal.x50.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:21) at com.android.tools.r8.internal.x50.error(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:1) at com.android.tools.r8.shaking.M.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:1962) at com.android.tools.r8.shaking.M.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:1643) at com.android.tools.r8.R8.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:1471) at com.android.tools.r8.R8.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:233) at com.android.tools.r8.R8.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:1547) at com.android.tools.r8.internal.to.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:28) ... 5 more Suppressed: java.lang.RuntimeException: com.android.tools.r8.internal.g: Missing class java.lang.foreign.AddressLayout (referenced from: java.lang.foreign.AddressLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.POINTER and 6 other contexts) Missing class java.lang.foreign.Arena (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.allocateInt(java.lang.foreign.Arena) and 7 other contexts) Missing class java.lang.foreign.FunctionDescriptor (referenced from: java.lang.invoke.MethodHandle com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.handle$lambda$3$lambda$2$lambda$0(com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder, java.lang.foreign.MemoryLayout, java.lang.foreign.MemoryLayout[], java.lang.foreign.MemorySegment)) Missing class java.lang.foreign.Linker$Option (referenced from: java.lang.invoke.MethodHandle com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.handle$lambda$3$lambda$2$lambda$0(com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder, java.lang.foreign.MemoryLayout, java.lang.foreign.MemoryLayout[], java.lang.foreign.MemorySegment)) Missing class java.lang.foreign.Linker (referenced from: java.lang.foreign.Linker com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.linker and 6 other contexts) Missing class java.lang.foreign.MemoryLayout$PathElement (referenced from: long com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.byteOffset(java.lang.foreign.MemoryLayout, java.lang.String) and 1 other context) Missing class java.lang.foreign.MemoryLayout (referenced from: java.lang.foreign.MemoryLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt$shortField$$inlined$scalarField$default$1.$layout and 46 other contexts) Missing class java.lang.foreign.MemorySegment (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.LinuxCLibrary$winsize.segment and 61 other contexts) Missing class java.lang.foreign.StructLayout (referenced from: java.lang.foreign.MemoryLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.StructLayout.getLayout()) Missing class java.lang.foreign.SymbolLookup (referenced from: java.lang.foreign.SymbolLookup com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.lookup and 6 other contexts) Missing class java.lang.foreign.ValueLayout$OfByte (referenced from: java.lang.foreign.ValueLayout$OfByte com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.BYTE and 2 other contexts) Missing class java.lang.foreign.ValueLayout$OfChar (referenced from: java.lang.foreign.ValueLayout$OfChar com.github.ajalt.mordant.terminal.terminalinterface.ffm.WinLayouts.WCHAR and 1 other context) Missing class java.lang.foreign.ValueLayout$OfInt (referenced from: java.lang.foreign.ValueLayout$OfInt com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.INT and 4 other contexts) Missing class java.lang.foreign.ValueLayout$OfLong (referenced from: java.lang.foreign.ValueLayout$OfLong com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.LONG and 2 other contexts) Missing class java.lang.foreign.ValueLayout$OfShort (referenced from: java.lang.foreign.ValueLayout$OfShort com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.SHORT and 4 other contexts) Missing class java.lang.foreign.ValueLayout (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.allocateInt(java.lang.foreign.Arena) and 2 other contexts) at com.android.tools.r8.internal.x50.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:29) at com.android.tools.r8.shaking.M.a(R8_8.5.35_9c55004e7c41a17b1ed47c4e1952cb6778b3dac6afb6afc113a2737c3cde13e0:1964) ... 10 more Caused by: [CIRCULAR REFERENCE: com.android.tools.r8.internal.g: Missing class java.lang.foreign.AddressLayout (referenced from: java.lang.foreign.AddressLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.POINTER and 6 other contexts) Missing class java.lang.foreign.Arena (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.allocateInt(java.lang.foreign.Arena) and 7 other contexts) Missing class java.lang.foreign.FunctionDescriptor (referenced from: java.lang.invoke.MethodHandle com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.handle$lambda$3$lambda$2$lambda$0(com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder, java.lang.foreign.MemoryLayout, java.lang.foreign.MemoryLayout[], java.lang.foreign.MemorySegment)) Missing class java.lang.foreign.Linker$Option (referenced from: java.lang.invoke.MethodHandle com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.handle$lambda$3$lambda$2$lambda$0(com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder, java.lang.foreign.MemoryLayout, java.lang.foreign.MemoryLayout[], java.lang.foreign.MemorySegment)) Missing class java.lang.foreign.Linker (referenced from: java.lang.foreign.Linker com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.linker and 6 other contexts) Missing class java.lang.foreign.MemoryLayout$PathElement (referenced from: long com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.byteOffset(java.lang.foreign.MemoryLayout, java.lang.String) and 1 other context) Missing class java.lang.foreign.MemoryLayout (referenced from: java.lang.foreign.MemoryLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt$shortField$$inlined$scalarField$default$1.$layout and 46 other contexts) Missing class java.lang.foreign.MemorySegment (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.LinuxCLibrary$winsize.segment and 61 other contexts) Missing class java.lang.foreign.StructLayout (referenced from: java.lang.foreign.MemoryLayout com.github.ajalt.mordant.terminal.terminalinterface.ffm.StructLayout.getLayout()) Missing class java.lang.foreign.SymbolLookup (referenced from: java.lang.foreign.SymbolLookup com.github.ajalt.mordant.terminal.terminalinterface.ffm.MethodHandlesHolder.lookup and 6 other contexts) Missing class java.lang.foreign.ValueLayout$OfByte (referenced from: java.lang.foreign.ValueLayout$OfByte com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.BYTE and 2 other contexts) Missing class java.lang.foreign.ValueLayout$OfChar (referenced from: java.lang.foreign.ValueLayout$OfChar com.github.ajalt.mordant.terminal.terminalinterface.ffm.WinLayouts.WCHAR and 1 other context) Missing class java.lang.foreign.ValueLayout$OfInt (referenced from: java.lang.foreign.ValueLayout$OfInt com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.INT and 4 other contexts) Missing class java.lang.foreign.ValueLayout$OfLong (referenced from: java.lang.foreign.ValueLayout$OfLong com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.LONG and 2 other contexts) Missing class java.lang.foreign.ValueLayout$OfShort (referenced from: java.lang.foreign.ValueLayout$OfShort com.github.ajalt.mordant.terminal.terminalinterface.ffm.Layouts.SHORT and 4 other contexts) Missing class java.lang.foreign.ValueLayout (referenced from: java.lang.foreign.MemorySegment com.github.ajalt.mordant.terminal.terminalinterface.ffm.FfmLayoutsKt.allocateInt(java.lang.foreign.Arena) and 2 other contexts)] ```

This is because I'm running minification on JDK <22, which is a requirement because I do not want to enforce users of the minified CLI to run on JDK 22+. Mordant is targeting Java 8 bytecode, my project is targeting Java 11 bytecode. R8 reads and emits Java 11 bytecode, but it needs to know about these classes.

The classes are visible if

Defining a toolchain is:

    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(22)
        vendor = JvmVendorSpec.ADOPTIUM // Temurin
    }

Your project's CI and I guess your local env is using JDK 22, so your case falls into the first bullet point. My library/CLI app uses the second approach (I was running on JDK 21 (LTS) and JDK 11 toolchain to represent the lowest-supported user).

I'm opening this issue to share my findings, and maybe trigger you into changing the testing approach or finding a way to make the second bullet point work. A 3rd party dependency of a project shouldn't dictate what JDKs another build is required to be using.

An alternative workaround to "solve" the R8 issue is to keep using lower JDKs, but also

    api(libs.clikt) {
                // See https://github.com/ajalt/mordant/issues/233
        // https://github.com/ajalt/mordant/releases/tag/3.0.0
        // > Added new terminal implementation that uses the Foreign Function and Memory (FFM) API added in JDK 22.
        exclude(group: "com.github.ajalt.mordant", "module": "mordant-jvm-ffm")
        exclude(group: "com.github.ajalt.mordant", "module": "mordant-jvm-ffm-jvm")
    }

which I don't know what effect it has.

ajalt commented 1 day ago

I'm not totally clear on what change you're asking for here. The FFM module uses APIs introduced in JDK 22, so it has to compile with that version.


Your workaround is valid, although you might prefer the simpler method of specifying just the modules you need rather than depending on the whole mordant package and excluding parts of it:

implementation("com.github.ajalt.mordant:mordant-core:$mordantVersion")
implementation("com.github.ajalt.mordant:mordant-jvm-jna:$mordantVersion")