Skip to content

Commit 98cbc68

Browse files
committed
Add JLine FFM based terminal provider
1 parent 3932a4f commit 98cbc68

File tree

7 files changed

+349
-8
lines changed

7 files changed

+349
-8
lines changed

sdk/mx.sdk/mx_sdk_vm_impl.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ def svm_experimental_options(experimental_options):
126126
'--features=org.graalvm.launcher.PolyglotLauncherFeature',
127127
'--tool:all',
128128
] + svm_experimental_options(['-H:-ParseRuntimeOptions']),
129+
extra_jvm_args=[
130+
'--enable-native-access=org.graalvm.shadowed.jline',
131+
],
129132
is_main_launcher=True,
130133
default_symlinks=True,
131134
is_sdk_launcher=True,

sdk/mx.sdk/suite.py

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@
135135
"version": "3.28.0",
136136
},
137137
},
138-
139138
"JLINE_TERMINAL": {
140139
"moduleName": "org.jline.terminal",
141140
"digest": "sha512:abe0ad0303e5eb81b549301dfdcf34aace14495240816f14302d193296c7a8be31488e468d18a215976b8e4e8fa29f72d830e492eed7d4a6f9f04c81a6e36c3c",
@@ -146,7 +145,6 @@
146145
"version": "3.28.0",
147146
},
148147
},
149-
150148
"JLINE_BUILTINS": {
151149
"moduleName": "org.jline.builtins",
152150
"digest": "sha512:189d893405170a3edc624a6b822a8a394a2f8b623c23aed9e015d4b018b232307408b6038322719155fc7da7e9c04a9bb0a76c8521f49dd86a5f84ea3880acb6",
@@ -157,6 +155,16 @@
157155
"version": "3.28.0",
158156
},
159157
},
158+
"JLINE_TERMINAL_FFM": {
159+
"moduleName": "org.jline.terminal.ffm",
160+
"digest": "sha512:e5839b04a2fd6119a11c6bc16e05203af88512039d85551b19d6e87c358a325ed5eb7051022a225e2641357c99d9c4121817a4795c50cf79a13b6b9d537cee96",
161+
"sourceDigest": "sha512:c651ae99fe1f453d9b3d22913e2fb003c11ff9c43621bedd7508fa322b49f15c3d93cf146c00f2e1f9dd939f3ca9009a52ee407b63fa3f3d4f5c997a1efba139",
162+
"maven": {
163+
"groupId": "org.jline",
164+
"artifactId": "jline-terminal-ffm",
165+
"version": "3.28.0",
166+
},
167+
},
160168
"LLVM_ORG" : {
161169
"version" : "20.1.4-1-ga7183f5a17-bg217527b869",
162170
"host" : "https://lafo.ssw.uni-linz.ac.at/pub/llvm-org",
@@ -684,14 +692,17 @@
684692
"graalCompilerSourceEdition": "ignore",
685693
},
686694
"org.graalvm.shadowed.org.jline": {
687-
# shaded JLINE_*
695+
# shaded custom JLine bundle
688696
"subDir": "src",
689697
"sourceDirs": ["src"],
690698
"javaCompliance": "17+",
691699
"spotbugs": "false",
692700
"requires": [
693701
"java.logging",
694702
],
703+
"dependencies": [
704+
"sdk:NATIVEIMAGE",
705+
],
695706
"shadedDependencies": [
696707
"sdk:JLINE_READER",
697708
"sdk:JLINE_TERMINAL",
@@ -732,6 +743,11 @@ class UniversalDetector {
732743
String getDetectedCharset() { return null; }
733744
}""",
734745
},
746+
# Adds calls to initialize logging. This is a convenient way to enable JLine logging
747+
# in order to verify which terminal provider is used at runtime.
748+
"org/jline/terminal/TerminalBuilder.java": {
749+
"private TerminalBuilder\\(\\) {}": "private TerminalBuilder() { org.graalvm.shadowed.org.jline.terminal.JLineLoggingSupport.init(); }"
750+
},
735751
# Remove dependency on JLine's native library (would require shading and deployment of the library)
736752
# The native library is a fallback for functionality that is otherwise done via accessing
737753
# JDK internals via reflection.
@@ -755,16 +771,24 @@ class UniversalDetector {
755771
import org.graalvm.shadowed.org.jline.terminal.Terminal;
756772
import org.graalvm.shadowed.org.jline.terminal.impl.exec.ExecTerminalProvider;
757773
""",
774+
# \\x7b is to avoid the opening curly brace, which confuses the suite.py parser.
775+
# The commented out closing curly brace at the end is to match the opening
776+
# brace, again, to make the suite.py parser happy.
758777
"static TerminalProvider load\\(String name\\) throws IOException \\x7b":
759778
"""
760779
static TerminalProvider load(String name) throws IOException {
761780
switch (name) {
762781
case \"exec\":
763782
return new ExecTerminalProvider();
783+
case \"ffm\":
784+
TerminalProvider p = org.graalvm.shadowed.org.jline.terminal.impl.ffm.FFMTerminalProviderLoader.load();
785+
if (p != null) {
786+
return p;
787+
}
764788
default:
765-
if (Boolean.TRUE) { // to avoid unreachable code
766-
throw new IOException(\"Unable to find terminal provider \" + name);
767-
}
789+
if (Boolean.TRUE) { // to avoid unreachable code below the switch
790+
throw new IOException(\"Unable to find terminal provider \" + name);
791+
}
768792
}
769793
// }
770794
""",
@@ -778,6 +802,48 @@ class UniversalDetector {
778802
"jacoco": "exclude",
779803
"graalCompilerSourceEdition": "ignore",
780804
},
805+
"org.graalvm.shadowed.org.jline.jdk22": {
806+
# Shaded JLINE_TERMINAL_FFM as jdk22 overlay for the shaded JLine bundle
807+
# Needs --enable-native-access=org.graalvm.shadowed.jline
808+
"subDir": "src",
809+
"sourceDirs": ["src"],
810+
"javaCompliance": "22+",
811+
"spotbugs": "false",
812+
"requires": [
813+
"java.logging",
814+
],
815+
"dependencies": [
816+
"org.graalvm.shadowed.org.jline",
817+
"sdk:NATIVEIMAGE",
818+
],
819+
"shadedDependencies": [
820+
"sdk:JLINE_TERMINAL_FFM",
821+
],
822+
"class": "ShadedLibraryProject",
823+
"shade": {
824+
"packages": {
825+
"org.jline": "org.graalvm.shadowed.org.jline",
826+
},
827+
"exclude": [
828+
"META-INF/MANIFEST.MF",
829+
# we patch the JLine's service loading mechanism with
830+
# hard-coded set of supported services, see one of the patches below
831+
"META-INF/services/**",
832+
"META-INF/maven/**",
833+
# We have our own native-image configuration (in the overlaid project)
834+
"META-INF/native-image/**",
835+
],
836+
},
837+
"description": "JLINE FFM based Terminal service provider.",
838+
"overlayTarget" : "org.graalvm.shadowed.org.jline",
839+
"multiReleaseJarVersion" : "22",
840+
"ignoreSrcGenForOverlayMap": "true",
841+
"allowsJavadocWarnings": True,
842+
"noMavenJavadoc": True,
843+
"javac.lint.overrides": 'none',
844+
"jacoco": "exclude",
845+
"graalCompilerSourceEdition": "ignore",
846+
},
781847
"org.graalvm.maven.downloader" : {
782848
"subDir" : "src",
783849
"sourceDirs" : ["src"],
@@ -1135,7 +1201,10 @@ class UniversalDetector {
11351201
"graalCompilerSourceEdition": "ignore",
11361202
},
11371203
"JLINE3": {
1138-
# shaded JLINE_*
1204+
# Custom shaded JLine bundle (with FFM terminal provider on JDK22+)
1205+
# One must pass --enable-native-access=org.graalvm.shadowed.jline, otherwise
1206+
# JLine silently falls back to exec provider on POSIX, and with a warning
1207+
# to "Dumb" provider on Windows
11391208
"moduleInfo": {
11401209
"name": "org.graalvm.shadowed.jline",
11411210
"requires": [
@@ -1161,6 +1230,10 @@ class UniversalDetector {
11611230
"dependencies": [
11621231
"org.graalvm.shadowed.org.jline",
11631232
],
1233+
"distDependencies": [
1234+
"sdk:NATIVEIMAGE",
1235+
"sdk:POLYGLOT",
1236+
],
11641237
"description": "JLINE3 shaded module.",
11651238
"allowsJavadocWarnings": True,
11661239
"license": "BSD-new",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# errors
2+
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
3+
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
4+
# warnings
5+
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
6+
org.eclipse.jdt.core.compiler.problem.deprecation=ignore
7+
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
8+
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
9+
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
10+
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
11+
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
12+
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
13+
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
14+
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
15+
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore
16+
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
17+
org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore
18+
org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
19+
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
20+
org.eclipse.jdt.core.compiler.problem.unusedTypeArgumentsForMethodInvocation=ignore
21+
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=ignore
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package org.graalvm.shadowed.org.jline.terminal.impl.ffm;
42+
43+
import java.lang.foreign.FunctionDescriptor;
44+
import java.lang.foreign.Linker;
45+
import java.lang.foreign.MemoryLayout;
46+
import java.lang.foreign.StructLayout;
47+
import java.lang.foreign.ValueLayout;
48+
49+
import org.graalvm.nativeimage.hosted.Feature;
50+
import org.graalvm.nativeimage.hosted.RuntimeForeignAccess;
51+
import org.graalvm.shadowed.org.jline.terminal.spi.TerminalProvider;
52+
53+
public class FFMTerminalProviderLoader {
54+
public static TerminalProvider load() {
55+
return new org.graalvm.shadowed.org.jline.terminal.impl.ffm.FfmTerminalProvider();
56+
}
57+
}
58+
59+
class FFMTerminalProviderFeature implements Feature {
60+
private record DowncallDesc(FunctionDescriptor fd, Linker.Option... options) {
61+
}
62+
63+
private static StructLayout WIN_COORD_LAYOUT = MemoryLayout.structLayout(ValueLayout.JAVA_SHORT.withName("x"), ValueLayout.JAVA_SHORT.withName("y"));
64+
65+
// Downcalls from CLibrary.java
66+
private static DowncallDesc[] getUnixDowncalls() {
67+
return new DowncallDesc[]{
68+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS), Linker.Option.firstVariadicArg(2)), // ioctl
69+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)), // isatty
70+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS)), // tcsetattr
71+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS)), // tcgetattr
72+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)), // ttyname_r
73+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)) // openpty
74+
};
75+
}
76+
77+
// Downcalls from Kernel32
78+
private static DowncallDesc[] getWindowsDowncalls() {
79+
return new DowncallDesc[]{
80+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)), // WaitForSingleObject
81+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT)), // GetStdHandle
82+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.ADDRESS,
83+
ValueLayout.JAVA_INT, ValueLayout.ADDRESS)), // FormatMessageW
84+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_SHORT)), // SetConsoleTextAttribute
85+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)), // SetConsoleMode
86+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS)), // GetConsoleMode
87+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)), // SetConsoleTitleW
88+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, WIN_COORD_LAYOUT)), // SetConsoleCursorPosition
89+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_CHAR, ValueLayout.JAVA_INT, WIN_COORD_LAYOUT, ValueLayout.ADDRESS)), // FillConsoleOutputCharacterW
90+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_SHORT, ValueLayout.JAVA_INT, WIN_COORD_LAYOUT, ValueLayout.ADDRESS)), // FillConsoleOutputAttribute
91+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS)), // WriteConsoleW
92+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS)), // ReadConsoleInputW
93+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS)), // PeekConsoleInputW
94+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS)), // GetConsoleScreenBufferInfo
95+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS, WIN_COORD_LAYOUT, ValueLayout.ADDRESS)), // ScrollConsoleScreenBufferW
96+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT)), // GetLastError
97+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)), // GetFileType
98+
new DowncallDesc(FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT)), // _get_osfhandle
99+
};
100+
}
101+
102+
private static DowncallDesc[] getDowncalls() {
103+
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
104+
return getWindowsDowncalls();
105+
}
106+
return getUnixDowncalls();
107+
}
108+
109+
public void duringSetup(DuringSetupAccess access) {
110+
for (DowncallDesc downcall : getDowncalls()) {
111+
RuntimeForeignAccess.registerForDowncall(downcall.fd(), (Object[]) downcall.options());
112+
}
113+
}
114+
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# AttributedCharSequence reads a system property (for runtime configuration) in static ctor
22
# OSUtils detects current OS capabilities in static ctor
3+
# FFMTerminalProviderFeature is noop on JDK<=21, on JDK22+ it is overridden using mx overlay mechanism,
4+
# and it registers FFM downcalls for FFM terminal provider
35
Args = --initialize-at-build-time=org.graalvm.shadowed.org.jline \
4-
--initialize-at-run-time=org.graalvm.shadowed.org.jline.utils.AttributedCharSequence,org.graalvm.shadowed.org.jline.utils.OSUtils \
6+
-H:+UnlockExperimentalVMOptions -H:+ForeignAPISupport -H:-UnlockExperimentalVMOptions \
7+
--features=org.graalvm.shadowed.org.jline.terminal.impl.ffm.FFMTerminalProviderFeature \
8+
--initialize-at-run-time=org.graalvm.shadowed.org.jline.utils.AttributedCharSequence,org.graalvm.shadowed.org.jline.utils.OSUtils,org.graalvm.shadowed.org.jline.terminal.impl.ffm \

0 commit comments

Comments
 (0)