Skip to content

Commit aca8682

Browse files
committed
Test against GraalVM native-image
Added an example of a native application that uses the driver and is built using GraalVM. The application runs the existing tour applications for the sync, legacy and reactive driver APIs. JAVA-3580
1 parent 3d42c1f commit aca8682

File tree

11 files changed

+935
-0
lines changed

11 files changed

+935
-0
lines changed

graalvm-native-image-app/build.gradle

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
plugins {
18+
id 'application'
19+
id 'org.graalvm.buildtools.native' version '0.9.23'
20+
}
21+
22+
application {
23+
mainClass = 'com.mongodb.internal.graalvm.NativeImageApp'
24+
}
25+
26+
// see https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html
27+
graalvmNative {
28+
metadataRepository {
29+
enabled = true
30+
version = '0.3.6'
31+
}
32+
agent {
33+
// Executing the `run` Gradle task with the tracing agent
34+
// https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/
35+
// requires running Gradle with GraalVM despite the toolchain for the task already being GraalVM.
36+
// The same is true about executing the `metadataCopy` Gradle task.
37+
// This may be a manifestation of an issue with the `org.graalvm.buildtools.native` plugin.
38+
enabled = false
39+
defaultMode = 'standard'
40+
metadataCopy {
41+
inputTaskNames.add('run')
42+
outputDirectories.add('src/main/resources/META-INF/native-image')
43+
mergeWithExisting = false
44+
}
45+
}
46+
binaries {
47+
configureEach {
48+
buildArgs.add('--strict-image-heap')
49+
// see class initialization report is generated at `graalvm/build/native/nativeCompile/reports`,
50+
// informing us on the kind of initialization for each Java class
51+
buildArgs.add('-H:+PrintClassInitialization')
52+
// see the "registerResource" entries in the `native-image` built-time output,
53+
// informing us on the resources included in the native image being built
54+
buildArgs.add('-H:Log=registerResource:5')
55+
}
56+
main {
57+
sharedLibrary = false
58+
def mainClassName = application.mainClass.get()
59+
imageName = mainClassName.substring(mainClassName.lastIndexOf('.') + 1, mainClassName.length())
60+
quickBuild = true
61+
// See the "Apply" entries in the `native-image` built-time output, informing us on
62+
// the build configuration files (https://www.graalvm.org/latest/reference-manual/native-image/overview/BuildConfiguration/)
63+
// and the reachability metadata files (https://www.graalvm.org/latest/reference-manual/native-image/metadata/)
64+
// which are applied at build time.
65+
verbose = true
66+
}
67+
}
68+
}
69+
70+
dependencies {
71+
// we intentionally depend here on the driver artifacts instead of depending on compiled classes
72+
implementation project(path:':bson', configuration:'archives')
73+
implementation project(path:':driver-core', configuration:'archives')
74+
implementation project(path:':driver-sync', configuration:'archives')
75+
implementation project(path:':driver-reactive-streams', configuration:'archives')
76+
implementation project(path:':driver-legacy', configuration:'archives')
77+
// note that as a result of these `sourceSets` dependencies, `driver-sync/src/test/resources/logback-test.xml` is used
78+
implementation project(':driver-sync').sourceSets.test.output
79+
implementation project(':driver-legacy').sourceSets.test.output
80+
implementation project(':driver-reactive-streams').sourceSets.test.output
81+
implementation "org.mongodb:mongodb-crypt:$mongoCryptVersion"
82+
implementation 'org.slf4j:slf4j-api:2.0.12'
83+
implementation 'ch.qos.logback:logback-classic:1.5.3'
84+
implementation platform("io.projectreactor:reactor-bom:$projectReactorVersion")
85+
implementation 'io.projectreactor:reactor-core'
86+
}

graalvm-native-image-app/readme.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# graalvm-native-image-app
2+
3+
## About
4+
This is an example of a native application that uses the driver and is built using
5+
[GraalVM native image](https://www.graalvm.org/latest/reference-manual/native-image/).
6+
7+
## Contributor Guide
8+
9+
This guide assumes you are using a shell capable of running [Bash](https://www.gnu.org/software/bash/) scripts.
10+
11+
### Prepare the development environment
12+
13+
#### Install GraalVM
14+
15+
[GraalVM for JDK 21 Community](https://github.com/graalvm/graalvm-ce-builds/releases/tag/jdk-21.0.2) is required
16+
in addition to the JDK you are using for running [Gradle](https://gradle.org/) when building the driver.
17+
Note that GraalVM for JDK 21 Community is [available](https://sdkman.io/jdks#graalce) via [SDKMAN!](https://sdkman.io/).
18+
19+
#### Configure environment variables pointing to JDKs.
20+
21+
Assuming that the JDK you are using for running Gradle is for Java SE 17, export the following variables
22+
(your values may differ):
23+
24+
```bash
25+
export JDK17="/Users/valentin.kovalenko/.sdkman/candidates/java/17.0.10-librca/"
26+
export JDK21_GRAALVM="/Users/valentin.kovalenko/.sdkman/candidates/java/21.0.2-graalce/"
27+
```
28+
29+
### Build-related commands
30+
31+
Run from the driver project root directory:
32+
33+
| # | Command | Description |
34+
|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
35+
| 1 | `env JAVA_HOME=${JDK17} ./gradlew -PjavaVersion=21 :graalvm-native-image-app:nativeCompile` | Build the application relying on the reachability metadata stored in `graalvm-native-image-app/src/main/resources/META-INF/native-image`. |
36+
| 2 | `env JAVA_HOME=${JDK17} ./gradlew :graalvm-native-image-app:clean && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew -PjavaVersion=21 -Pagent :graalvm-native-image-app:run && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew :graalvm-native-image-app:metadataCopy` | Collect the reachability metadata and update the files storing it. Do this only if building/running the application fails otherwise. |
37+
| 3 | `./graalvm-native-image-app/build/native/nativeCompile/NativeImageApp` | Run the application that has been built. |
38+
| 4 | `env JAVA_HOME=${JDK17} ./gradlew -PjavaVersion=21 :graalvm-native-image-app:nativeRun` | Run the application using Gradle, build it if necessary relying on the stored reachability metadata. |
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.mongodb.internal.graalvm;
17+
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
21+
import javax.annotation.Nullable;
22+
import java.util.List;
23+
import java.util.Objects;
24+
import java.util.stream.Stream;
25+
26+
final class NativeImageApp {
27+
private static final Logger LOGGER = LoggerFactory.getLogger(NativeImageApp.class);
28+
29+
public static void main(final String[] args) {
30+
LOGGER.info("java.vendor={}, java.vm.name={}, java.version={}",
31+
System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.version"));
32+
List<Throwable> errors = Stream.<ThrowingRunnable>of(
33+
() -> gridfs.GridFSTour.main(args),
34+
() -> documentation.CausalConsistencyExamples.main(args),
35+
() -> documentation.ChangeStreamSamples.main(args),
36+
() -> tour.ClientSideEncryptionAutoEncryptionSettingsTour.main(args),
37+
() -> tour.ClientSideEncryptionExplicitEncryptionAndDecryptionTour.main(args),
38+
() -> tour.ClientSideEncryptionExplicitEncryptionOnlyTour.main(args),
39+
() -> tour.ClientSideEncryptionQueryableEncryptionTour.main(args),
40+
() -> tour.ClientSideEncryptionSimpleTour.main(args),
41+
() -> tour.Decimal128QuickTour.main(args),
42+
() -> tour.PojoQuickTour.main(args),
43+
() -> tour.QuickTour.main(args),
44+
() -> tour.Decimal128LegacyAPIQuickTour.main(args),
45+
() -> reactivestreams.gridfs.GridFSTour.main(args),
46+
// This tour is broken and hangs even when run by a JVM.
47+
// See https://jira.mongodb.org/browse/JAVA-5364.
48+
// () -> reactivestreams.tour.ClientSideEncryptionAutoEncryptionSettingsTour.main(args),
49+
() -> reactivestreams.tour.ClientSideEncryptionExplicitEncryptionAndDecryptionTour.main(args),
50+
() -> reactivestreams.tour.ClientSideEncryptionExplicitEncryptionOnlyTour.main(args),
51+
() -> reactivestreams.tour.ClientSideEncryptionQueryableEncryptionTour.main(args),
52+
() -> reactivestreams.tour.ClientSideEncryptionSimpleTour.main(args),
53+
() -> reactivestreams.tour.PojoQuickTour.main(args),
54+
() -> reactivestreams.tour.QuickTour.main(args)
55+
).map(ThrowingRunnable::runAndCatch)
56+
.filter(Objects::nonNull)
57+
.toList();
58+
if (!errors.isEmpty()) {
59+
AssertionError error = new AssertionError(String.format("%d %s failed",
60+
errors.size(), errors.size() == 1 ? "application" : "applications"));
61+
errors.forEach(error::addSuppressed);
62+
throw error;
63+
}
64+
}
65+
66+
private NativeImageApp() {
67+
}
68+
69+
private interface ThrowingRunnable {
70+
void run() throws Exception;
71+
72+
@Nullable
73+
default Throwable runAndCatch() {
74+
try {
75+
run();
76+
} catch (Exception | AssertionError e) {
77+
return e;
78+
}
79+
return null;
80+
}
81+
}
82+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
[
2+
{
3+
"name":"com.mongodb.crypt.capi.CAPI$mongocrypt_crypto_fn",
4+
"methods":[{"name":"crypt","parameterTypes":["com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_status_t"] }]
5+
},
6+
{
7+
"name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hash_fn",
8+
"methods":[{"name":"hash","parameterTypes":["com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_status_t"] }]
9+
},
10+
{
11+
"name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hmac_fn",
12+
"methods":[{"name":"hmac","parameterTypes":["com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","com.mongodb.crypt.capi.CAPI$mongocrypt_status_t"] }]
13+
},
14+
{
15+
"name":"com.mongodb.crypt.capi.CAPI$mongocrypt_log_fn_t",
16+
"methods":[{"name":"log","parameterTypes":["int","com.mongodb.crypt.capi.CAPI$cstring","int","com.sun.jna.Pointer"] }]
17+
},
18+
{
19+
"name":"com.mongodb.crypt.capi.CAPI$mongocrypt_random_fn",
20+
"methods":[{"name":"random","parameterTypes":["com.sun.jna.Pointer","com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t","int","com.mongodb.crypt.capi.CAPI$mongocrypt_status_t"] }]
21+
},
22+
{
23+
"name":"com.mongodb.internal.graalvm.NativeImageApp",
24+
"methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }]
25+
},
26+
{
27+
"name":"com.sun.jna.Callback"
28+
},
29+
{
30+
"name":"com.sun.jna.CallbackReference",
31+
"methods":[{"name":"getCallback","parameterTypes":["java.lang.Class","com.sun.jna.Pointer","boolean"] }, {"name":"getFunctionPointer","parameterTypes":["com.sun.jna.Callback","boolean"] }, {"name":"getNativeString","parameterTypes":["java.lang.Object","boolean"] }, {"name":"initializeThread","parameterTypes":["com.sun.jna.Callback","com.sun.jna.CallbackReference$AttachOptions"] }]
32+
},
33+
{
34+
"name":"com.sun.jna.CallbackReference$AttachOptions"
35+
},
36+
{
37+
"name":"com.sun.jna.FromNativeConverter",
38+
"methods":[{"name":"nativeType","parameterTypes":[] }]
39+
},
40+
{
41+
"name":"com.sun.jna.IntegerType",
42+
"fields":[{"name":"value"}]
43+
},
44+
{
45+
"name":"com.sun.jna.JNIEnv"
46+
},
47+
{
48+
"name":"com.sun.jna.Native",
49+
"methods":[{"name":"dispose","parameterTypes":[] }, {"name":"fromNative","parameterTypes":["com.sun.jna.FromNativeConverter","java.lang.Object","java.lang.reflect.Method"] }, {"name":"fromNative","parameterTypes":["java.lang.Class","java.lang.Object"] }, {"name":"fromNative","parameterTypes":["java.lang.reflect.Method","java.lang.Object"] }, {"name":"nativeType","parameterTypes":["java.lang.Class"] }, {"name":"toNative","parameterTypes":["com.sun.jna.ToNativeConverter","java.lang.Object"] }]
50+
},
51+
{
52+
"name":"com.sun.jna.Native$ffi_callback",
53+
"methods":[{"name":"invoke","parameterTypes":["long","long","long"] }]
54+
},
55+
{
56+
"name":"com.sun.jna.NativeMapped",
57+
"methods":[{"name":"toNative","parameterTypes":[] }]
58+
},
59+
{
60+
"name":"com.sun.jna.Pointer",
61+
"fields":[{"name":"peer"}],
62+
"methods":[{"name":"<init>","parameterTypes":["long"] }]
63+
},
64+
{
65+
"name":"com.sun.jna.PointerType",
66+
"fields":[{"name":"pointer"}]
67+
},
68+
{
69+
"name":"com.sun.jna.Structure",
70+
"fields":[{"name":"memory"}, {"name":"typeInfo"}],
71+
"methods":[{"name":"autoRead","parameterTypes":[] }, {"name":"autoWrite","parameterTypes":[] }, {"name":"getTypeInfo","parameterTypes":[] }, {"name":"newInstance","parameterTypes":["java.lang.Class","long"] }]
72+
},
73+
{
74+
"name":"com.sun.jna.Structure$ByValue"
75+
},
76+
{
77+
"name":"com.sun.jna.Structure$FFIType$FFITypes",
78+
"fields":[{"name":"ffi_type_double"}, {"name":"ffi_type_float"}, {"name":"ffi_type_longdouble"}, {"name":"ffi_type_pointer"}, {"name":"ffi_type_sint16"}, {"name":"ffi_type_sint32"}, {"name":"ffi_type_sint64"}, {"name":"ffi_type_sint8"}, {"name":"ffi_type_uint16"}, {"name":"ffi_type_uint32"}, {"name":"ffi_type_uint64"}, {"name":"ffi_type_uint8"}, {"name":"ffi_type_void"}]
79+
},
80+
{
81+
"name":"com.sun.jna.WString",
82+
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
83+
},
84+
{
85+
"name":"java.lang.Boolean",
86+
"fields":[{"name":"TYPE"}, {"name":"value"}],
87+
"methods":[{"name":"<init>","parameterTypes":["boolean"] }, {"name":"getBoolean","parameterTypes":["java.lang.String"] }]
88+
},
89+
{
90+
"name":"java.lang.Byte",
91+
"fields":[{"name":"TYPE"}, {"name":"value"}],
92+
"methods":[{"name":"<init>","parameterTypes":["byte"] }]
93+
},
94+
{
95+
"name":"java.lang.Character",
96+
"fields":[{"name":"TYPE"}, {"name":"value"}],
97+
"methods":[{"name":"<init>","parameterTypes":["char"] }]
98+
},
99+
{
100+
"name":"java.lang.Class",
101+
"methods":[{"name":"getComponentType","parameterTypes":[] }]
102+
},
103+
{
104+
"name":"java.lang.Double",
105+
"fields":[{"name":"TYPE"}, {"name":"value"}],
106+
"methods":[{"name":"<init>","parameterTypes":["double"] }]
107+
},
108+
{
109+
"name":"java.lang.Float",
110+
"fields":[{"name":"TYPE"}, {"name":"value"}],
111+
"methods":[{"name":"<init>","parameterTypes":["float"] }]
112+
},
113+
{
114+
"name":"java.lang.Integer",
115+
"fields":[{"name":"TYPE"}, {"name":"value"}],
116+
"methods":[{"name":"<init>","parameterTypes":["int"] }]
117+
},
118+
{
119+
"name":"java.lang.Long",
120+
"fields":[{"name":"TYPE"}, {"name":"value"}],
121+
"methods":[{"name":"<init>","parameterTypes":["long"] }]
122+
},
123+
{
124+
"name":"java.lang.Object",
125+
"methods":[{"name":"toString","parameterTypes":[] }]
126+
},
127+
{
128+
"name":"java.lang.Short",
129+
"fields":[{"name":"TYPE"}, {"name":"value"}],
130+
"methods":[{"name":"<init>","parameterTypes":["short"] }]
131+
},
132+
{
133+
"name":"java.lang.String",
134+
"methods":[{"name":"<init>","parameterTypes":["byte[]"] }, {"name":"<init>","parameterTypes":["byte[]","java.lang.String"] }, {"name":"getBytes","parameterTypes":[] }, {"name":"getBytes","parameterTypes":["java.lang.String"] }, {"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }, {"name":"toCharArray","parameterTypes":[] }]
135+
},
136+
{
137+
"name":"java.lang.System",
138+
"methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }]
139+
},
140+
{
141+
"name":"java.lang.UnsatisfiedLinkError",
142+
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
143+
},
144+
{
145+
"name":"java.lang.Void",
146+
"fields":[{"name":"TYPE"}]
147+
},
148+
{
149+
"name":"java.lang.reflect.Method",
150+
"methods":[{"name":"getParameterTypes","parameterTypes":[] }, {"name":"getReturnType","parameterTypes":[] }]
151+
},
152+
{
153+
"name":"java.nio.Buffer",
154+
"methods":[{"name":"position","parameterTypes":[] }]
155+
},
156+
{
157+
"name":"java.nio.ByteBuffer",
158+
"methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }]
159+
},
160+
{
161+
"name":"java.nio.CharBuffer",
162+
"methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }]
163+
},
164+
{
165+
"name":"java.nio.DoubleBuffer",
166+
"methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }]
167+
},
168+
{
169+
"name":"java.nio.FloatBuffer",
170+
"methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }]
171+
},
172+
{
173+
"name":"java.nio.IntBuffer",
174+
"methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }]
175+
},
176+
{
177+
"name":"java.nio.LongBuffer",
178+
"methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }]
179+
},
180+
{
181+
"name":"java.nio.ShortBuffer",
182+
"methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }]
183+
}
184+
]

0 commit comments

Comments
 (0)