Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit ee47652

Browse files
authored
[build] Add support for JDK 21 (#1287)
Context: 5bb0d24 Context: 4273e5c Context: 0355acf Context: dotnet/android#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 5bb0d24: 1. [Install JDK-21][0] 2. Run `dotnet build -t:Prepare`, overriding `$(JdksRoot)` to refer to the JDK-21 installation directory: 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 JDK-21's `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 4273e5c and 0355acf, 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: // "Equivalent" Java for JNI purposes /* partial */ class Outer { /* partial */ class Inner { public Inner (Outer outer) { … } // note added constructor parameter } } 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.<init>(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, which 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
1 parent fe00cef commit ee47652

5 files changed

Lines changed: 57 additions & 5 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

‎src/Java.Base/Java.Lang/StringBuffer.cs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,15 @@
44

55
namespaceJava.Lang{
66
partialclassStringBuffer:IEnumerable,IEnumerable<char>{
7+
8+
#if JAVA_API_21
9+
IAppendable?IAppendable.Append(charc)=>
10+
Append(c);
11+
IAppendable?IAppendable.Append(ICharSequence?s)=>
12+
Append(s);
13+
IAppendable?IAppendable.Append(ICharSequence?s,inta,intb)=>
14+
Append(s,a,b);
15+
#endif // JAVA_API_21
16+
717
}
818
}

‎src/Java.Base/Java.Lang/StringBuilder.cs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,15 @@
44

55
namespaceJava.Lang{
66
partialclassStringBuilder:IEnumerable,IEnumerable<char>{
7+
8+
#if JAVA_API_21
9+
IAppendable?IAppendable.Append(charc)=>
10+
Append(c);
11+
IAppendable?IAppendable.Append(ICharSequence?s)=>
12+
Append(s);
13+
IAppendable?IAppendable.Append(ICharSequence?s,inta,intb)=>
14+
Append(s,a,b);
15+
#endif // JAVA_API_21
16+
717
}
818
}

‎src/Java.Base/Java.Lang/Thread.cs‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespaceJava.Lang{
2+
partialclassThread{
3+
4+
#if JAVA_API_21
5+
partialinterfaceIBuilder{
6+
partialclassIOfPlatformInvoker{
7+
IBuilder?IBuilder.InheritInheritableThreadLocals(boolvalue)=>
8+
InheritInheritableThreadLocals(value);
9+
IBuilder?IBuilder.Name(string?name)=>
10+
Name(name);
11+
IBuilder?IBuilder.Name(string?name,longv)=>
12+
Name(name,v);
13+
IBuilder?IBuilder.UncaughtExceptionHandler(IUncaughtExceptionHandler?u)=>
14+
UncaughtExceptionHandler(u);
15+
}
16+
partialclassIOfVirtualInvoker{
17+
IBuilder?IBuilder.InheritInheritableThreadLocals(boolvalue)=>
18+
InheritInheritableThreadLocals(value);
19+
IBuilder?IBuilder.Name(string?name)=>
20+
Name(name);
21+
IBuilder?IBuilder.Name(string?name,longv)=>
22+
Name(name,v);
23+
IBuilder?IBuilder.UncaughtExceptionHandler(IUncaughtExceptionHandler?u)=>
24+
UncaughtExceptionHandler(u);
25+
}
26+
}
27+
#endif // JAVA_API_21
28+
29+
}
30+
}

‎src/Xamarin.Android.Tools.Bytecode/Methods.cs‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,16 @@ void UpdateParametersFromMethodParametersAttribute (ParameterInfo[] parameters)
300300
MethodParameterAccessFlags.Final|MethodParameterAccessFlags.Synthetic;
301301
varpinfo=methodParams.ParameterInfo;
302302
intstartIndex=0;
303-
while(startIndex<pinfo.Count&&
304-
((pinfo[startIndex].AccessFlags&OuterThis1)==OuterThis1||
305-
(pinfo[startIndex].AccessFlags&OuterThis2)==OuterThis2)){
303+
while(IsConstructor&&
304+
startIndex<pinfo.Count&&
305+
((pinfo[startIndex].AccessFlags&OuterThis1)==OuterThis1||
306+
(pinfo[startIndex].AccessFlags&OuterThis2)==OuterThis2)){
306307
startIndex++;
307308
}
308309
Debug.Assert(
309310
parameters.Length==pinfo.Count-startIndex,
310-
$"Unexpected number of method parameters in `{DeclaringType.FullJniName}.{Name}{Descriptor}`: expected {parameters.Length}, got {pinfo.Count-startIndex}");
311+
$"Unexpected number of method parameters in `{DeclaringType.FullJniName}.{Name}{Descriptor}`: expected {parameters.Length}, got {pinfo.Count-startIndex}! "+
312+
$"pinfo.Count={pinfo.Count}, startIndex={startIndex}, pinfo={string.Join(", ",pinfo.Select(p =>p.ToString()))}");
311313
intend=Math.Min(parameters.Length,pinfo.Count-startIndex);
312314
for(inti=0;i<end;++i){
313315
varp=pinfo[i+startIndex];

0 commit comments

Comments
 (0)