From 0400cece077aa42d87eb42b2526d1849018511ab Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 7 Jan 2025 14:17:12 -0500 Subject: [PATCH] [build] Add support for JDK 21 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: 5bb0d24b97c54648ba13fa8ad3abf9c2405baa79 Context: 4273e5cee65c0e8a951702d47cd9f0d96f32a14a Context: 0355acf8c16eedb78545cdd0d58f623b5486ed73 Context: https://github.com/dotnet/android/pull/9651 Does dotnet/android build with JDK-21? We don't know! But in order to answer that question, dotnet/java-interop needs to be able to build under JDK-21; a'la 5bb0d24b: 1. [Install JDK-21][0] 2. Run `dotnet build -t:Prepare`, overriding `$(JdksRoot)`: dotnet build -t:Prepare Java.Interop.sln -p:JdksRoot=/Library/Java/JavaVirtualMachines/microsoft-21.jdk/Contents/Home 3. Build: `dotnet build` Unfortunately, this *fails* for three reasons: 1. `class-parse` crashes when processing `java.base.jmod`. 2. `src/Java.Base` needs updates to bind JDK-21's `java.base.jmod`. 3. On Linux and Windows, Gradle 8.1 and JDK-21 don't mix. ~~ class-parse ~~ This only impacts Debug builds of dotnet/java-interop, but: % dotnet "bin/Debug-net8.0/class-parse.dll" \ "$HOME/android-toolchain/jdk-21/jmods/java.base.jmod" \ "-o=obj/Debug-net8.0//mcw/api.xml" … Process terminated. Assertion failed. Unexpected number of method parameters in `Ljdk/internal/org/objectweb/asm/commons/JSRInlinerAdapter$Instantiation;.get(Ljava/lang/Object;)Ljava/lang/Object;`: expected 1, got 0 at Xamarin.Android.Tools.Bytecode.MethodInfo.UpdateParametersFromMethodParametersAttribute(ParameterInfo[] parameters) in …/src/Xamarin.Android.Tools.Bytecode/Methods.cs:line 308 … The assertion? var pinfo = methodParams.ParameterInfo; int startIndex = 0; while (startIndex < pinfo.Count && pinfo [startIndex].AccessFlags.HasFlag (.Synthetic)) startIndex++; Debug.Assert (parameters.Length == pinfo.Count - startIndex, …); This is part of 4273e5ce and 0355acf8, attempting to "skip over" the constructor parameters which non-static inner classes have which contain the outer class instance: // Java class Outer { /* non-static */ class Inner { public Inner () { … } } } At the ABI boundary, the `Outer.Inner` constructor is actually: /* partial */ class Outer { /* partial */ class Inner { public Inner (Outer outer) { … } } } and we need to skip over the first parameter. Which brings us to `jdk/internal/org/objectweb/asm/commons/JSRInlinerAdapter.Instantiation.get()`: % mkdir _x % unzip $HOME/android-toolchain/jdk-21/jmods/java.base.jmod -d _x % dotnet bin/Debug-net8.0/class-parse.dll --dump \ _x/classes/jdk/internal/org/objectweb/asm/commons/JSRInlinerAdapter\$Instantiation.class … 8: get (Ljava/lang/Object;)Ljava/lang/Object; Public, Bridge, Synthetic Code(6, Unknown[LineNumberTable](6), LocalVariableTableAttribute(LocalVariableTableEntry(Name='this', Descriptor='Ljdk/internal/org/objectweb/asm/commons/JSRInlinerAdapter$Instantiation;', StartPC=0, Index=0))) MethodParametersAttribute(MethodParameterInfo(Name='', AccessFlags=Final, Synthetic)) The parameter for `JSRInlinerAdapter.Instantiation.get(Object)` is `Synthetic`, causing us to skip over it, which is why we have a parameter mismatch. The thing is, this parameter *shouldn't* be skipped; the skipping is intended for *constructor* parameters! Update the code so that the loop only occurs for constructors. This allows `class-parse` to *not assert*, allowing `src/Java.Base` to build. ~~ Gradle ~~ Via dotnet/android#9651, `gradle` fails when building `tools/java-source-utils`, but only on Linux and Windows: "/mnt/vss/_work/1/s/xamarin-android/external/Java.Interop/build-tools/gradle/gradlew" -d --stacktrace --no-daemon -PjavaSourceVer=11 -PjavaTargetVer=11 jar … [org.gradle.internal.buildevents.BuildExceptionReporter] [org.gradle.internal.buildevents.BuildExceptionReporter] FAILURE: Build failed with an exception. [org.gradle.internal.buildevents.BuildExceptionReporter] [org.gradle.internal.buildevents.BuildExceptionReporter] * What went wrong: [org.gradle.internal.buildevents.BuildExceptionReporter] Could not open settings generic class cache for settings file '/mnt/vss/_work/1/s/xamarin-android/external/Java.Interop/tools/java-source-utils/settings.gradle' (/home/cloudtest/.gradle/caches/8.1.1/scripts/aiw0k2bokig45bv5yvkog3o3j). [org.gradle.internal.buildevents.BuildExceptionReporter] > BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 65 [org.gradle.internal.buildevents.BuildExceptionReporter] [org.gradle.internal.buildevents.BuildExceptionReporter] * Try: [org.gradle.internal.buildevents.BuildExceptionReporter] > Run with --scan to get full insights. [org.gradle.internal.buildevents.BuildExceptionReporter] [org.gradle.internal.buildevents.BuildExceptionReporter] * Exception is: [org.gradle.internal.buildevents.BuildExceptionReporter] org.gradle.cache.CacheOpenException: Could not open settings generic class cache for settings file '/mnt/vss/_work/1/s/xamarin-android/external/Java.Interop/tools/java-source-utils/settings.gradle' (/home/cloudtest/.gradle/caches/8.1.1/scripts/aiw0k2bokig45bv5yvkog3o3j). [org.gradle.internal.buildevents.BuildExceptionReporter] at org.gradle.cache.internal.DefaultPersistentDirectoryStore.open(DefaultPersistentDirectoryStore.java:91) [org.gradle.internal.buildevents.BuildExceptionReporter] … [org.gradle.internal.buildevents.BuildExceptionReporter] Caused by: BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 65 [org.gradle.internal.buildevents.BuildExceptionReporter] at org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler.compileScript(DefaultScriptCompilationHandler.java:147) [org.gradle.internal.buildevents.BuildExceptionReporter] … [org.gradle.internal.buildevents.BuildExceptionReporter] Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 65 [org.gradle.internal.buildevents.BuildExceptionReporter] at groovyjarjarasm.asm.ClassReader.(ClassReader.java:199) 🤔 As per the [Gradle Compatibility Matrix][1], Java 21 requires Gradle 8.5 or later. (No idea why this works on macOS…) Bump to Gradle 8.12, whic is the current latest stable version. ~~ TODO ~~ While dotnet/java-interop now *builds* with JDK-21, unit tests don't fully pass. In particular, `tests/Xamarin.Android.Tools.Bytecode-Tests` will need to be updated because `javac` output has changed. (Again.) [0]: https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-21 [1]: https://docs.gradle.org/8.12/userguide/compatibility.html --- .../gradle/wrapper/gradle-wrapper.properties | 2 +- src/Java.Base/Java.Lang/StringBuffer.cs | 10 +++++++ src/Java.Base/Java.Lang/StringBuilder.cs | 10 +++++++ src/Java.Base/Java.Lang/Thread.cs | 30 +++++++++++++++++++ src/Xamarin.Android.Tools.Bytecode/Methods.cs | 10 ++++--- 5 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 src/Java.Base/Java.Lang/Thread.cs diff --git a/build-tools/gradle/gradle/wrapper/gradle-wrapper.properties b/build-tools/gradle/gradle/wrapper/gradle-wrapper.properties index fae08049a..d6e308a63 100644 --- a/build-tools/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/build-tools/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/Java.Base/Java.Lang/StringBuffer.cs b/src/Java.Base/Java.Lang/StringBuffer.cs index 13a798613..8488622b4 100644 --- a/src/Java.Base/Java.Lang/StringBuffer.cs +++ b/src/Java.Base/Java.Lang/StringBuffer.cs @@ -4,5 +4,15 @@ namespace Java.Lang { partial class StringBuffer : IEnumerable, IEnumerable { + +#if JAVA_API_21 + IAppendable? IAppendable.Append (char c) => + Append (c); + IAppendable? IAppendable.Append (ICharSequence? s) => + Append (s); + IAppendable? IAppendable.Append (ICharSequence? s, int a, int b) => + Append (s, a, b); +#endif // JAVA_API_21 + } } diff --git a/src/Java.Base/Java.Lang/StringBuilder.cs b/src/Java.Base/Java.Lang/StringBuilder.cs index 0eecdaa24..f71efa4df 100644 --- a/src/Java.Base/Java.Lang/StringBuilder.cs +++ b/src/Java.Base/Java.Lang/StringBuilder.cs @@ -4,5 +4,15 @@ namespace Java.Lang { partial class StringBuilder : IEnumerable, IEnumerable { + +#if JAVA_API_21 + IAppendable? IAppendable.Append (char c) => + Append (c); + IAppendable? IAppendable.Append (ICharSequence? s) => + Append (s); + IAppendable? IAppendable.Append (ICharSequence? s, int a, int b) => + Append (s, a, b); +#endif // JAVA_API_21 + } } diff --git a/src/Java.Base/Java.Lang/Thread.cs b/src/Java.Base/Java.Lang/Thread.cs new file mode 100644 index 000000000..d13a6bb4e --- /dev/null +++ b/src/Java.Base/Java.Lang/Thread.cs @@ -0,0 +1,30 @@ +namespace Java.Lang { + partial class Thread { + +#if JAVA_API_21 + partial interface IBuilder { + partial class IOfPlatformInvoker { + IBuilder? IBuilder.InheritInheritableThreadLocals (bool value) => + InheritInheritableThreadLocals (value); + IBuilder? IBuilder.Name (string? name) => + Name (name); + IBuilder? IBuilder.Name (string? name, long v) => + Name (name, v); + IBuilder? IBuilder.UncaughtExceptionHandler (IUncaughtExceptionHandler? u) => + UncaughtExceptionHandler (u); + } + partial class IOfVirtualInvoker { + IBuilder? IBuilder.InheritInheritableThreadLocals (bool value) => + InheritInheritableThreadLocals (value); + IBuilder? IBuilder.Name (string? name) => + Name (name); + IBuilder? IBuilder.Name (string? name, long v) => + Name (name, v); + IBuilder? IBuilder.UncaughtExceptionHandler (IUncaughtExceptionHandler? u) => + UncaughtExceptionHandler (u); + } + } +#endif // JAVA_API_21 + + } +} diff --git a/src/Xamarin.Android.Tools.Bytecode/Methods.cs b/src/Xamarin.Android.Tools.Bytecode/Methods.cs index 6059b12eb..a0fdfba78 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Methods.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Methods.cs @@ -300,14 +300,16 @@ void UpdateParametersFromMethodParametersAttribute (ParameterInfo[] parameters) MethodParameterAccessFlags.Final | MethodParameterAccessFlags.Synthetic; var pinfo = methodParams.ParameterInfo; int startIndex = 0; - while (startIndex < pinfo.Count && - ((pinfo [startIndex].AccessFlags & OuterThis1) == OuterThis1 || - (pinfo [startIndex].AccessFlags & OuterThis2) == OuterThis2)) { + while (IsConstructor && + startIndex < pinfo.Count && + ((pinfo [startIndex].AccessFlags & OuterThis1) == OuterThis1 || + (pinfo [startIndex].AccessFlags & OuterThis2) == OuterThis2)) { startIndex++; } Debug.Assert ( parameters.Length == pinfo.Count - startIndex, - $"Unexpected number of method parameters in `{DeclaringType.FullJniName}.{Name}{Descriptor}`: expected {parameters.Length}, got {pinfo.Count - startIndex}"); + $"Unexpected number of method parameters in `{DeclaringType.FullJniName}.{Name}{Descriptor}`: expected {parameters.Length}, got {pinfo.Count - startIndex}! " + + $"pinfo.Count={pinfo.Count}, startIndex={startIndex}, pinfo={string.Join (", ", pinfo.Select (p => p.ToString ()))}"); int end = Math.Min (parameters.Length, pinfo.Count - startIndex); for (int i = 0; i < end; ++i) { var p = pinfo [i + startIndex];