23
23
*/
24
24
package com .sun .jna ;
25
25
26
+ import com .oracle .svm .core .jdk .NativeLibrarySupport ;
27
+ import com .oracle .svm .core .jdk .PlatformNativeLibrarySupport ;
28
+ import com .oracle .svm .hosted .FeatureImpl .BeforeAnalysisAccessImpl ;
29
+ import org .graalvm .nativeimage .Platform ;
26
30
import org .graalvm .nativeimage .hosted .Feature ;
27
31
32
+ import java .io .File ;
33
+ import java .io .IOException ;
34
+ import java .nio .file .Files ;
35
+ import java .util .Collections ;
36
+ import java .util .List ;
37
+
28
38
/**
29
39
* Feature for use at build time on GraalVM, which enables static JNI support for JNA.
30
40
*
36
46
*
37
47
* <p>This class extends the base {@link com.sun.jna.JavaNativeAccess} feature by providing JNA's JNI layer statically,
38
48
* so that no library unpacking step needs to take place.
49
+ *
50
+ * @since 5.15.0
51
+ * @author Sam Gammon ([email protected] )
52
+ * @author Dario Valdespino ([email protected] )
39
53
*/
40
54
public final class SubstrateStaticJNA extends AbstractJNAFeature {
55
+ /**
56
+ * Name for the FFI native library used during static linking by Native Image.
57
+ */
58
+ private static final String FFI_LINK_NAME = "ffi" ;
59
+
60
+ /**
61
+ * Name for the JNI Dispatch native library used during static linking by Native Image.
62
+ */
63
+ private static final String JNA_LINK_NAME = "jnidispatch" ;
64
+
65
+ /**
66
+ * Name prefix used by native functions from the JNI Dispatch library.
67
+ */
68
+ private static final String JNA_NATIVE_LAYOUT = "com_sun_jna_Native" ;
69
+
70
+ /**
71
+ * Name of the JNI Dispatch static library on UNIX-based platforms.
72
+ */
73
+ private static final String JNI_DISPATCH_UNIX_NAME = "libjnidispatch.a" ;
74
+
75
+ /**
76
+ * Name of the JNI Dispatch static library on Windows.
77
+ */
78
+ private static final String JNI_DISPATCH_WINDOWS_NAME = "jnidispatch.lib" ;
79
+
80
+ /**
81
+ * Name of the FFI static library on UNIX-based platforms.
82
+ */
83
+ private static final String FFI_UNIX_NAME = "libffi.a" ;
84
+
85
+ /**
86
+ * Name of the FFI static library on Windows.
87
+ */
88
+ private static final String FFI_WINDOWS_NAME = "ffi.lib" ;
89
+
90
+ /**
91
+ * Returns the name of the static JNI Dispatch library for the current platform. On UNIX-based systems,
92
+ * {@link #JNI_DISPATCH_UNIX_NAME} is used; on Windows, {@link #JNI_DISPATCH_WINDOWS_NAME} is returned instead.
93
+ *
94
+ * @see #getStaticLibraryResource
95
+ * @return The JNI Dispatch library name for the current platform.
96
+ */
97
+ private static String getStaticLibraryFileName () {
98
+ if (Platform .includedIn (Platform .WINDOWS .class )) return JNI_DISPATCH_WINDOWS_NAME ;
99
+ if (Platform .includedIn (Platform .LINUX .class )) return JNI_DISPATCH_UNIX_NAME ;
100
+ if (Platform .includedIn (Platform .DARWIN .class )) return JNI_DISPATCH_UNIX_NAME ;
101
+
102
+ // If the current platform is not in the Platform class, this code would not run at all
103
+ throw new UnsupportedOperationException ("Current platform does not support static linking" );
104
+ }
105
+
106
+ /**
107
+ * Returns the name of the static FFI library for the current platform. On UNIX-based systems,
108
+ * {@link #FFI_UNIX_NAME} is used; on Windows, {@link #FFI_WINDOWS_NAME} is returned instead.
109
+ *
110
+ * @see #getStaticLibraryResource
111
+ * @return The FFI library name for the current platform.
112
+ */
113
+ private static String getFFILibraryFileName () {
114
+ if (Platform .includedIn (Platform .WINDOWS .class )) return FFI_WINDOWS_NAME ;
115
+ if (Platform .includedIn (Platform .LINUX .class )) return FFI_UNIX_NAME ;
116
+ if (Platform .includedIn (Platform .DARWIN .class )) return FFI_UNIX_NAME ;
117
+
118
+ // If the current platform is not in the Platform class, this code would not run at all
119
+ throw new UnsupportedOperationException ("Current platform does not support static FFI" );
120
+ }
121
+
122
+ /**
123
+ * Returns the full path to the static JNI Dispatch library embedded in the JAR, accounting for platform-specific
124
+ * library names.
125
+ *
126
+ * @see #getStaticLibraryFileName()
127
+ * @return The JNI Dispatch library resource path for the current platform.
128
+ */
129
+ private static String getStaticLibraryResource () {
130
+ return "/com/sun/jna/" + com .sun .jna .Platform .RESOURCE_PREFIX + "/" + getStaticLibraryFileName ();
131
+ }
132
+
133
+ /**
134
+ * Returns the full path to the static FFI library which JNA depends on, accounting for platform-specific
135
+ * library names.
136
+ *
137
+ * @see #getFFILibraryFileName()
138
+ * @return The FFI library resource path for the current platform.
139
+ */
140
+ private static String getFFILibraryResource () {
141
+ return "/com/sun/jna/" + com .sun .jna .Platform .RESOURCE_PREFIX + "/" + getFFILibraryFileName ();
142
+ }
143
+
144
+ /**
145
+ * Extracts a library resource and returns the file it was extracted to.
146
+ *
147
+ * @param resource Resource path for the library to extract.
148
+ * @param filename Expected filename for the library.
149
+ * @return The extracted library file.
150
+ */
151
+ private static File unpackLibrary (String resource , String filename ) {
152
+ // Unpack the static library from resources so Native Image can use it
153
+ File extractedLib ;
154
+ try {
155
+ extractedLib = Native .extractFromResourcePath (resource , Native .class .getClassLoader ());
156
+
157
+ // The library is extracted into a file with a `.tmp` name, which will not be picked up by the linker
158
+ // We need to rename it first using the platform-specific convention or the build will fail
159
+ File platformLib = new File (extractedLib .getParentFile (), filename );
160
+ if (!extractedLib .renameTo (platformLib )) throw new IllegalStateException ("Renaming extract file failed" );
161
+ extractedLib = platformLib ;
162
+ } catch (IOException e ) {
163
+ throw new RuntimeException ("Failed to extract native dispatch library from resources" , e );
164
+ }
165
+ return extractedLib ;
166
+ }
167
+
41
168
@ Override
42
169
public String getDescription () {
43
170
return "Enables optimized static access to JNA at runtime" ;
@@ -48,8 +175,34 @@ public boolean isInConfiguration(IsInConfigurationAccess access) {
48
175
return access .findClassByName (JavaNativeAccess .NATIVE_LAYOUT ) != null ;
49
176
}
50
177
178
+ @ Override
179
+ public List <Class <? extends Feature >> getRequiredFeatures () {
180
+ return Collections .singletonList (JavaNativeAccess .class );
181
+ }
182
+
51
183
@ Override
52
184
public void beforeAnalysis (BeforeAnalysisAccess access ) {
53
- //
185
+ var nativeLibraries = NativeLibrarySupport .singleton ();
186
+ var platformLibraries = PlatformNativeLibrarySupport .singleton ();
187
+
188
+ // Register as a built-in library with Native Image and set the name prefix used by native symbols
189
+ nativeLibraries .preregisterUninitializedBuiltinLibrary (JNA_LINK_NAME );
190
+ platformLibraries .addBuiltinPkgNativePrefix (JNA_NATIVE_LAYOUT );
191
+
192
+ // Extract the main JNA library from the platform-specific resource path; next, extract the FFI
193
+ // library it depends on
194
+ unpackLibrary (getFFILibraryResource (), getFFILibraryFileName ());
195
+ var extractedLib = unpackLibrary (getStaticLibraryResource (), getStaticLibraryFileName ());
196
+
197
+ // WARNING: the static JNI linking feature is unstable and may be removed in the future;
198
+ // this code uses the access implementation directly in order to register the static library. We
199
+ // inform the Native Image compiler that JNA depends on `ffi`, so that it forces it to load first
200
+ // when JNA is initialized at image runtime.
201
+ var nativeLibsImpl = ((BeforeAnalysisAccessImpl ) access ).getNativeLibraries ();
202
+ nativeLibsImpl .addStaticNonJniLibrary (FFI_LINK_NAME );
203
+ nativeLibsImpl .addStaticJniLibrary (JNA_LINK_NAME , FFI_LINK_NAME );
204
+
205
+ // Enhance the Native Image lib paths so the injected static libraries are available to the linker
206
+ nativeLibsImpl .getLibraryPaths ().add (extractedLib .getParentFile ().getAbsolutePath ());
54
207
}
55
208
}
0 commit comments