From 4b52a0ee2f2e9ec24ca4ebd74d5c8728b03d2319 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Thu, 14 Mar 2024 01:58:55 -0600 Subject: [PATCH 1/8] 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 --- .evergreen/.evg.yml | 20 + .evergreen/javaConfig.bash | 3 + .evergreen/run-graalvm-native-image-app.sh | 25 + ...eEncryptionAutoEncryptionSettingsTour.java | 11 +- ...onExplicitEncryptionAndDecryptionTour.java | 13 +- ...eEncryptionExplicitEncryptionOnlyTour.java | 12 +- .../tour/ClientSideEncryptionSimpleTour.java | 6 +- .../CausalConsistencyExamples.java | 15 +- ...eEncryptionAutoEncryptionSettingsTour.java | 12 +- ...onExplicitEncryptionAndDecryptionTour.java | 13 +- ...eEncryptionExplicitEncryptionOnlyTour.java | 12 +- .../tour/ClientSideEncryptionSimpleTour.java | 6 +- graalvm-native-image-app/build.gradle | 87 ++++ graalvm-native-image-app/readme.md | 48 ++ .../internal/graalvm/NativeImageApp.java | 133 ++++++ .../META-INF/native-image/jni-config.json | 184 ++++++++ .../predefined-classes-config.json | 7 + .../META-INF/native-image/proxy-config.json | 2 + .../META-INF/native-image/reflect-config.json | 446 ++++++++++++++++++ .../native-image/resource-config.json | 57 +++ .../native-image/serialization-config.json | 8 + gradle.properties | 2 +- gradle/javaToolchain.gradle | 49 ++ settings.gradle | 1 + 24 files changed, 1134 insertions(+), 38 deletions(-) create mode 100755 .evergreen/run-graalvm-native-image-app.sh create mode 100644 graalvm-native-image-app/build.gradle create mode 100644 graalvm-native-image-app/readme.md create mode 100644 graalvm-native-image-app/src/main/com/mongodb/internal/graalvm/NativeImageApp.java create mode 100644 graalvm-native-image-app/src/main/resources/META-INF/native-image/jni-config.json create mode 100644 graalvm-native-image-app/src/main/resources/META-INF/native-image/predefined-classes-config.json create mode 100644 graalvm-native-image-app/src/main/resources/META-INF/native-image/proxy-config.json create mode 100644 graalvm-native-image-app/src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 graalvm-native-image-app/src/main/resources/META-INF/native-image/resource-config.json create mode 100644 graalvm-native-image-app/src/main/resources/META-INF/native-image/serialization-config.json diff --git a/.evergreen/.evg.yml b/.evergreen/.evg.yml index 055dcca7266..a321334c36a 100644 --- a/.evergreen/.evg.yml +++ b/.evergreen/.evg.yml @@ -882,6 +882,15 @@ functions: params: file: src/results.json + "run graalvm native image app": + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + JAVA_VERSION="${JAVA_VERSION}" .evergreen/run-graalvm-native-image-app.sh + # Anchors pre: @@ -1641,6 +1650,10 @@ tasks: VERSION: latest TOPOLOGY: replica_set - func: run socks5 tests + - name: "graalvm-native-image-app" + commands: + - func: "bootstrap mongo-orchestration" + - func: "run graalvm native image app" axes: - id: version display_name: MongoDB Version @@ -2286,3 +2299,10 @@ buildvariants: tasks: - name: testazurekms_task_group batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + +- matrix_name: "graalvm-native-image-app" + matrix_spec: { version: [ "5.0", "7.0" ], topology: [ "replicaset" ], auth: [ "noauth" ], ssl: [ "nossl" ], + jdk: [ "jdk21" ], os: [ "linux" ] } + display_name: "GraalVM native image app: ${version} ${topology} ${auth} ${ssl} ${jdk} ${os}" + tasks: + - name: "graalvm-native-image-app" diff --git a/.evergreen/javaConfig.bash b/.evergreen/javaConfig.bash index ba5d43a6b59..0b0c9125265 100644 --- a/.evergreen/javaConfig.bash +++ b/.evergreen/javaConfig.bash @@ -4,6 +4,9 @@ export JDK8="/opt/java/jdk8" export JDK11="/opt/java/jdk11" export JDK17="/opt/java/jdk17" export JDK21="/opt/java/jdk21" +# note that `JDK21_GRAALVM` is used in `run-graalvm-native-image-app.sh` +# by dynamically constructing the variable name +export JDK21_GRAALVM="/opt/java/jdk21-graalce" if [ -d "$JDK17" ]; then export JAVA_HOME=$JDK17 diff --git a/.evergreen/run-graalvm-native-image-app.sh b/.evergreen/run-graalvm-native-image-app.sh new file mode 100755 index 00000000000..e864b2db491 --- /dev/null +++ b/.evergreen/run-graalvm-native-image-app.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Supported/used environment variables: +# MONGODB_URI The connection string to use, including credentials and topology info. +# JAVA_VERSION The Java SE version for Gradle toolchain. + +set -o errexit + +RELATIVE_DIR_PATH="$(dirname "${BASH_SOURCE[0]:-$0}")" +source "${RELATIVE_DIR_PATH}/javaConfig.bash" + +echo "MONGODB_URI: ${MONGODB_URI}" +echo "JAVA_HOME: ${JAVA_HOME}" +readonly JDK_GRAALVM_VAR_NAME="JDK${JAVA_VERSION}_GRAALVM" +readonly JDK_GRAALVM="${!JDK_GRAALVM_VAR_NAME}" +echo "The JDK distribution for running Gradle is" +echo "$("${JAVA_HOME}"/bin/java --version)" +echo "The Java SE version for the Gradle toolchain is ${JAVA_VERSION}" +echo "The GraalVM JDK distribution expected to be found at \`${JDK_GRAALVM}\` by the Gradle toolchain functionality is" +echo "$("${JDK_GRAALVM}"/bin/java --version)" +echo "The Gradle version is" +./gradlew --version + +echo "Building and running the GraalVM native image app" +./gradlew -PjavaVersion=${JAVA_VERSION} -Dorg.mongodb.test.uri=${MONGODB_URI} :graalvm-native-image-app:nativeRun diff --git a/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionAutoEncryptionSettingsTour.java b/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionAutoEncryptionSettingsTour.java index a575e24dec1..e1b3e24f00a 100644 --- a/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionAutoEncryptionSettingsTour.java +++ b/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionAutoEncryptionSettingsTour.java @@ -64,11 +64,14 @@ public static void main(final String[] args) { }}); }}; + MongoClientSettings commonClientSettings = ( + args.length == 0 + ? MongoClientSettings.builder() + : MongoClientSettings.builder().applyConnectionString(new ConnectionString(args[0]))) + .build(); String keyVaultNamespace = "admin.datakeys"; ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() - .keyVaultMongoClientSettings(MongoClientSettings.builder() - .applyConnectionString(new ConnectionString("mongodb://localhost")) - .build()) + .keyVaultMongoClientSettings(commonClientSettings) .keyVaultNamespace(keyVaultNamespace) .kmsProviders(kmsProviders) .build(); @@ -107,7 +110,7 @@ public static void main(final String[] args) { + "}")); }}).build(); - MongoClientSettings clientSettings = MongoClientSettings.builder() + MongoClientSettings clientSettings = MongoClientSettings.builder(commonClientSettings) .autoEncryptionSettings(autoEncryptionSettings) .build(); diff --git a/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionExplicitEncryptionAndDecryptionTour.java b/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionExplicitEncryptionAndDecryptionTour.java index 19dee06fa61..14d4e156668 100644 --- a/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionExplicitEncryptionAndDecryptionTour.java +++ b/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionExplicitEncryptionAndDecryptionTour.java @@ -69,9 +69,12 @@ public static void main(final String[] args) { }}; MongoNamespace keyVaultNamespace = new MongoNamespace("encryption.testKeyVault"); - - MongoClientSettings clientSettings = MongoClientSettings.builder().build(); - MongoClient mongoClient = MongoClients.create(clientSettings); + MongoClientSettings commonClientSettings = ( + args.length == 0 + ? MongoClientSettings.builder() + : MongoClientSettings.builder().applyConnectionString(new ConnectionString(args[0]))) + .build(); + MongoClient mongoClient = MongoClients.create(commonClientSettings); // Set up the key vault for this example MongoCollection keyVaultCollection = mongoClient.getDatabase(keyVaultNamespace.getDatabaseName()) @@ -96,9 +99,7 @@ public static void main(final String[] args) { // Create the ClientEncryption instance ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() - .keyVaultMongoClientSettings(MongoClientSettings.builder() - .applyConnectionString(new ConnectionString("mongodb://localhost")) - .build()) + .keyVaultMongoClientSettings(commonClientSettings) .keyVaultNamespace(keyVaultNamespace.getFullName()) .kmsProviders(kmsProviders) .build(); diff --git a/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionExplicitEncryptionOnlyTour.java b/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionExplicitEncryptionOnlyTour.java index feed7076b4b..ecd680d9ed8 100644 --- a/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionExplicitEncryptionOnlyTour.java +++ b/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionExplicitEncryptionOnlyTour.java @@ -69,8 +69,12 @@ public static void main(final String[] args) { }}; MongoNamespace keyVaultNamespace = new MongoNamespace("encryption.testKeyVault"); - - MongoClientSettings clientSettings = MongoClientSettings.builder() + MongoClientSettings commonClientSettings = ( + args.length == 0 + ? MongoClientSettings.builder() + : MongoClientSettings.builder().applyConnectionString(new ConnectionString(args[0]))) + .build(); + MongoClientSettings clientSettings = MongoClientSettings.builder(commonClientSettings) .autoEncryptionSettings(AutoEncryptionSettings.builder() .keyVaultNamespace(keyVaultNamespace.getFullName()) .kmsProviders(kmsProviders) @@ -102,9 +106,7 @@ public static void main(final String[] args) { // Create the ClientEncryption instance ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() - .keyVaultMongoClientSettings(MongoClientSettings.builder() - .applyConnectionString(new ConnectionString("mongodb://localhost")) - .build()) + .keyVaultMongoClientSettings(commonClientSettings) .keyVaultNamespace(keyVaultNamespace.getFullName()) .kmsProviders(kmsProviders) .build(); diff --git a/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionSimpleTour.java b/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionSimpleTour.java index 25fa9572575..a9246a8d45d 100644 --- a/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionSimpleTour.java +++ b/driver-reactive-streams/src/examples/reactivestreams/tour/ClientSideEncryptionSimpleTour.java @@ -18,6 +18,7 @@ package reactivestreams.tour; import com.mongodb.AutoEncryptionSettings; +import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; import com.mongodb.client.result.InsertOneResult; import com.mongodb.reactivestreams.client.MongoClient; @@ -64,7 +65,10 @@ public static void main(final String[] args) { .kmsProviders(kmsProviders) .build(); - MongoClientSettings clientSettings = MongoClientSettings.builder() + MongoClientSettings clientSettings = ( + args.length == 0 + ? MongoClientSettings.builder() + : MongoClientSettings.builder().applyConnectionString(new ConnectionString(args[0]))) .autoEncryptionSettings(autoEncryptionSettings) .build(); diff --git a/driver-sync/src/examples/documentation/CausalConsistencyExamples.java b/driver-sync/src/examples/documentation/CausalConsistencyExamples.java index 5435523e65a..ab37d9c21ee 100644 --- a/driver-sync/src/examples/documentation/CausalConsistencyExamples.java +++ b/driver-sync/src/examples/documentation/CausalConsistencyExamples.java @@ -17,6 +17,8 @@ package documentation; import com.mongodb.ClientSessionOptions; +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; @@ -42,8 +44,13 @@ public final class CausalConsistencyExamples { * @param args takes an optional single argument for the connection string */ public static void main(final String[] args) { - setupDatabase(); - MongoClient client = MongoClients.create(); + MongoClientSettings clientSettings = ( + args.length == 0 + ? MongoClientSettings.builder() + : MongoClientSettings.builder().applyConnectionString(new ConnectionString(args[0]))) + .build(); + setupDatabase(clientSettings); + MongoClient client = MongoClients.create(clientSettings); // Start Causal Consistency Example 1 // Example 1: Use a causally consistent session to ensure that the update occurs before the insert. @@ -81,8 +88,8 @@ public static void main(final String[] args) { // End Causal Consistency Example 2 } - private static void setupDatabase() { - MongoClient client = MongoClients.create(); + private static void setupDatabase(final MongoClientSettings clientSettings) { + MongoClient client = MongoClients.create(clientSettings); client.getDatabase("test").drop(); MongoDatabase database = client.getDatabase("test"); diff --git a/driver-sync/src/examples/tour/ClientSideEncryptionAutoEncryptionSettingsTour.java b/driver-sync/src/examples/tour/ClientSideEncryptionAutoEncryptionSettingsTour.java index bda4e9df1c1..9880c1f17fc 100644 --- a/driver-sync/src/examples/tour/ClientSideEncryptionAutoEncryptionSettingsTour.java +++ b/driver-sync/src/examples/tour/ClientSideEncryptionAutoEncryptionSettingsTour.java @@ -58,12 +58,14 @@ public static void main(final String[] args) { put("key", localMasterKey); }}); }}; - + MongoClientSettings commonClientSettings = ( + args.length == 0 + ? MongoClientSettings.builder() + : MongoClientSettings.builder().applyConnectionString(new ConnectionString(args[0]))) + .build(); String keyVaultNamespace = "encryption.__keyVault"; ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() - .keyVaultMongoClientSettings(MongoClientSettings.builder() - .applyConnectionString(new ConnectionString("mongodb://localhost")) - .build()) + .keyVaultMongoClientSettings(commonClientSettings) .keyVaultNamespace(keyVaultNamespace) .kmsProviders(kmsProviders) .build(); @@ -99,7 +101,7 @@ public static void main(final String[] args) { + "}")); }}).build(); - MongoClientSettings clientSettings = MongoClientSettings.builder() + MongoClientSettings clientSettings = MongoClientSettings.builder(commonClientSettings) .autoEncryptionSettings(autoEncryptionSettings) .build(); diff --git a/driver-sync/src/examples/tour/ClientSideEncryptionExplicitEncryptionAndDecryptionTour.java b/driver-sync/src/examples/tour/ClientSideEncryptionExplicitEncryptionAndDecryptionTour.java index 64811fc9eea..853f364c4bf 100644 --- a/driver-sync/src/examples/tour/ClientSideEncryptionExplicitEncryptionAndDecryptionTour.java +++ b/driver-sync/src/examples/tour/ClientSideEncryptionExplicitEncryptionAndDecryptionTour.java @@ -60,9 +60,12 @@ public static void main(final String[] args) { put("key", localMasterKey); }}); }}; - - MongoClientSettings clientSettings = MongoClientSettings.builder().build(); - MongoClient mongoClient = MongoClients.create(clientSettings); + MongoClientSettings commonClientSettings = ( + args.length == 0 + ? MongoClientSettings.builder() + : MongoClientSettings.builder().applyConnectionString(new ConnectionString(args[0]))) + .build(); + MongoClient mongoClient = MongoClients.create(commonClientSettings); // Set up the key vault for this example MongoNamespace keyVaultNamespace = new MongoNamespace("encryption.testKeyVault"); @@ -81,9 +84,7 @@ public static void main(final String[] args) { // Create the ClientEncryption instance ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() - .keyVaultMongoClientSettings(MongoClientSettings.builder() - .applyConnectionString(new ConnectionString("mongodb://localhost")) - .build()) + .keyVaultMongoClientSettings(commonClientSettings) .keyVaultNamespace(keyVaultNamespace.getFullName()) .kmsProviders(kmsProviders) .build(); diff --git a/driver-sync/src/examples/tour/ClientSideEncryptionExplicitEncryptionOnlyTour.java b/driver-sync/src/examples/tour/ClientSideEncryptionExplicitEncryptionOnlyTour.java index 8f7cd5ae62b..e50cc54e29c 100644 --- a/driver-sync/src/examples/tour/ClientSideEncryptionExplicitEncryptionOnlyTour.java +++ b/driver-sync/src/examples/tour/ClientSideEncryptionExplicitEncryptionOnlyTour.java @@ -62,8 +62,12 @@ public static void main(final String[] args) { }}; MongoNamespace keyVaultNamespace = new MongoNamespace("encryption.testKeyVault"); - - MongoClientSettings clientSettings = MongoClientSettings.builder() + MongoClientSettings commonClientSettings = ( + args.length == 0 + ? MongoClientSettings.builder() + : MongoClientSettings.builder().applyConnectionString(new ConnectionString(args[0]))) + .build(); + MongoClientSettings clientSettings = MongoClientSettings.builder(commonClientSettings) .autoEncryptionSettings(AutoEncryptionSettings.builder() .keyVaultNamespace(keyVaultNamespace.getFullName()) .kmsProviders(kmsProviders) @@ -87,9 +91,7 @@ public static void main(final String[] args) { // Create the ClientEncryption instance ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() - .keyVaultMongoClientSettings(MongoClientSettings.builder() - .applyConnectionString(new ConnectionString("mongodb://localhost")) - .build()) + .keyVaultMongoClientSettings(commonClientSettings) .keyVaultNamespace(keyVaultNamespace.getFullName()) .kmsProviders(kmsProviders) .build(); diff --git a/driver-sync/src/examples/tour/ClientSideEncryptionSimpleTour.java b/driver-sync/src/examples/tour/ClientSideEncryptionSimpleTour.java index 1a80597dfd4..de116fd3a62 100644 --- a/driver-sync/src/examples/tour/ClientSideEncryptionSimpleTour.java +++ b/driver-sync/src/examples/tour/ClientSideEncryptionSimpleTour.java @@ -17,6 +17,7 @@ package tour; import com.mongodb.AutoEncryptionSettings; +import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -59,7 +60,10 @@ public static void main(final String[] args) { .kmsProviders(kmsProviders) .build(); - MongoClientSettings clientSettings = MongoClientSettings.builder() + MongoClientSettings clientSettings = ( + args.length == 0 + ? MongoClientSettings.builder() + : MongoClientSettings.builder().applyConnectionString(new ConnectionString(args[0]))) .autoEncryptionSettings(autoEncryptionSettings) .build(); diff --git a/graalvm-native-image-app/build.gradle b/graalvm-native-image-app/build.gradle new file mode 100644 index 00000000000..1a62dc40d7f --- /dev/null +++ b/graalvm-native-image-app/build.gradle @@ -0,0 +1,87 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id 'application' + id 'org.graalvm.buildtools.native' version '0.9.23' +} + +application { + mainClass = 'com.mongodb.internal.graalvm.NativeImageApp' +} + +// see https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html +graalvmNative { + metadataRepository { + enabled = true + version = '0.3.6' + } + agent { + // Executing the `run` Gradle task with the tracing agent + // https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/ + // requires running Gradle with GraalVM despite the toolchain for the task already being GraalVM. + // The same is true about executing the `metadataCopy` Gradle task. + // This may be a manifestation of an issue with the `org.graalvm.buildtools.native` plugin. + enabled = false + defaultMode = 'standard' + metadataCopy { + inputTaskNames.add('run') + outputDirectories.add('src/main/resources/META-INF/native-image') + mergeWithExisting = false + } + } + binaries { + configureEach { + buildArgs.add('--strict-image-heap') + // see class initialization report is generated at `graalvm/build/native/nativeCompile/reports`, + // informing us on the kind of initialization for each Java class + buildArgs.add('-H:+PrintClassInitialization') + // see the "registerResource" entries in the `native-image` built-time output, + // informing us on the resources included in the native image being built + buildArgs.add('-H:Log=registerResource:5') + } + main { + sharedLibrary = false + def mainClassName = application.mainClass.get() + imageName = mainClassName.substring(mainClassName.lastIndexOf('.') + 1, mainClassName.length()) + quickBuild = true + // See the "Apply" entries in the `native-image` built-time output, informing us on + // the build configuration files (https://www.graalvm.org/latest/reference-manual/native-image/overview/BuildConfiguration/) + // and the reachability metadata files (https://www.graalvm.org/latest/reference-manual/native-image/metadata/) + // which are applied at build time. + verbose = true + } + } +} + +dependencies { + // we intentionally depend here on the driver artifacts instead of depending on compiled classes + implementation project(path:':bson', configuration:'archives') + implementation project(path:':driver-core', configuration:'archives') + implementation project(path:':driver-sync', configuration:'archives') + implementation project(path:':driver-reactive-streams', configuration:'archives') + implementation project(path:':driver-legacy', configuration:'archives') + // note that as a result of these `sourceSets` dependencies, `driver-sync/src/test/resources/logback-test.xml` is used + implementation project(':driver-core').sourceSets.test.output + implementation project(':driver-sync').sourceSets.test.output + implementation project(':driver-legacy').sourceSets.test.output + implementation project(':driver-reactive-streams').sourceSets.test.output + implementation "org.mongodb:mongodb-crypt:$mongoCryptVersion" + implementation 'org.slf4j:slf4j-api:2.0.12' + implementation 'ch.qos.logback:logback-classic:1.5.3' + implementation platform("io.projectreactor:reactor-bom:$projectReactorVersion") + implementation 'io.projectreactor:reactor-core' +} diff --git a/graalvm-native-image-app/readme.md b/graalvm-native-image-app/readme.md new file mode 100644 index 00000000000..7af8970e6af --- /dev/null +++ b/graalvm-native-image-app/readme.md @@ -0,0 +1,48 @@ +# graalvm-native-image-app + +## About +This is an example of a native application that uses the driver and is built using +[GraalVM native image](https://www.graalvm.org/latest/reference-manual/native-image/). + +## Contributor Guide + +This guide assumes you are using a shell capable of running [Bash](https://www.gnu.org/software/bash/) scripts. + +### Prepare the development environment + +#### Install GraalVM + +[GraalVM for JDK 21 Community](https://github.com/graalvm/graalvm-ce-builds/releases/tag/jdk-21.0.2) is required +in addition to the JDK you are using for running [Gradle](https://gradle.org/) when building the driver. +Note that GraalVM for JDK 21 Community is [available](https://sdkman.io/jdks#graalce) via [SDKMAN!](https://sdkman.io/). + +##### Explanation of the requirement + +* GraalVM Community is the only distribution of GraalVM for which it is possible to + specify a Gradle toolchain specification that matches only GraalVM + and does not match any other JDK. +* GraalVM for Java SE 21 is required because it is the latest released version at the moment, + and not supporting the build for multiple, especially older versions, simplifies things. + Releases of JDKs for Java SE 21 having a long-term support from most vendors + also makes this version more attractive. + +#### Configure environment variables pointing to JDKs. + +Assuming that the JDK you are using for running Gradle is for Java SE 17, export the following variables +(your values may differ): + +```bash +export JDK17="/Users/valentin.kovalenko/.sdkman/candidates/java/17.0.10-librca/" +export JDK21_GRAALVM="/Users/valentin.kovalenko/.sdkman/candidates/java/21.0.2-graalce/" +``` + +### Build-related commands + +Run from the driver project root directory: + +| # | Command | Description | +|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| +| 0 | `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`. | +| 1 | `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 before building the application only if building fails otherwise. | +| 2 | `./graalvm-native-image-app/build/native/nativeCompile/NativeImageApp` | Run the application that has been built. | +| 3 | `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. | diff --git a/graalvm-native-image-app/src/main/com/mongodb/internal/graalvm/NativeImageApp.java b/graalvm-native-image-app/src/main/com/mongodb/internal/graalvm/NativeImageApp.java new file mode 100644 index 00000000000..6afdb339315 --- /dev/null +++ b/graalvm-native-image-app/src/main/com/mongodb/internal/graalvm/NativeImageApp.java @@ -0,0 +1,133 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.graalvm; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; +import static com.mongodb.ClusterFixture.getConnectionStringSystemPropertyOrDefault; + +final class NativeImageApp { + private static final Logger LOGGER = LoggerFactory.getLogger(NativeImageApp.class); + + public static void main(final String[] args) { + LOGGER.info("java.vendor={}, java.vm.name={}, java.version={}", + System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.version")); + String[] arguments = new String[] {getConnectionStringSystemPropertyOrDefault()}; + List errors = Stream.of( + new ThrowingRunnable.Named(gridfs.GridFSTour.class, + () -> gridfs.GridFSTour.main(arguments)), + new ThrowingRunnable.Named(documentation.CausalConsistencyExamples.class, + () -> documentation.CausalConsistencyExamples.main(arguments)), + new ThrowingRunnable.Named(documentation.ChangeStreamSamples.class, + () -> documentation.ChangeStreamSamples.main(arguments)), + new ThrowingRunnable.Named(tour.ClientSideEncryptionAutoEncryptionSettingsTour.class, + () -> tour.ClientSideEncryptionAutoEncryptionSettingsTour.main(arguments)), + new ThrowingRunnable.Named(tour.ClientSideEncryptionExplicitEncryptionAndDecryptionTour.class, + () -> tour.ClientSideEncryptionExplicitEncryptionAndDecryptionTour.main(arguments)), + new ThrowingRunnable.Named(tour.ClientSideEncryptionExplicitEncryptionOnlyTour.class, + () -> tour.ClientSideEncryptionExplicitEncryptionOnlyTour.main(arguments)), + new ThrowingRunnable.Named(tour.ClientSideEncryptionQueryableEncryptionTour.class, + () -> tour.ClientSideEncryptionQueryableEncryptionTour.main(arguments)), + new ThrowingRunnable.Named(tour.ClientSideEncryptionSimpleTour.class, + () -> tour.ClientSideEncryptionSimpleTour.main(arguments)), + new ThrowingRunnable.Named(tour.Decimal128QuickTour.class, + () -> tour.Decimal128QuickTour.main(arguments)), + new ThrowingRunnable.Named(tour.PojoQuickTour.class, + () -> tour.PojoQuickTour.main(arguments)), + new ThrowingRunnable.Named(tour.QuickTour.class, + () -> tour.QuickTour.main(arguments)), + new ThrowingRunnable.Named(tour.Decimal128LegacyAPIQuickTour.class, + () -> tour.Decimal128LegacyAPIQuickTour.main(arguments)), + new ThrowingRunnable.Named(reactivestreams.gridfs.GridFSTour.class, + () -> reactivestreams.gridfs.GridFSTour.main(arguments)), + // This tour is broken and hangs even when run by a JVM. + // See https://jira.mongodb.org/browse/JAVA-5364. + // new ThrowingRunnable.Named(reactivestreams.tour.ClientSideEncryptionAutoEncryptionSettingsTour.class, + // () -> reactivestreams.tour.ClientSideEncryptionAutoEncryptionSettingsTour.main(arguments)), + new ThrowingRunnable.Named(reactivestreams.tour.ClientSideEncryptionExplicitEncryptionAndDecryptionTour.class, + () -> reactivestreams.tour.ClientSideEncryptionExplicitEncryptionAndDecryptionTour.main(arguments)), + new ThrowingRunnable.Named(reactivestreams.tour.ClientSideEncryptionExplicitEncryptionOnlyTour.class, + () -> reactivestreams.tour.ClientSideEncryptionExplicitEncryptionOnlyTour.main(arguments)), + new ThrowingRunnable.Named(reactivestreams.tour.ClientSideEncryptionQueryableEncryptionTour.class, + () -> reactivestreams.tour.ClientSideEncryptionQueryableEncryptionTour.main(arguments)), + new ThrowingRunnable.Named(reactivestreams.tour.ClientSideEncryptionSimpleTour.class, + () -> reactivestreams.tour.ClientSideEncryptionSimpleTour.main(arguments)), + new ThrowingRunnable.Named(reactivestreams.tour.PojoQuickTour.class, + () -> reactivestreams.tour.PojoQuickTour.main(arguments)), + new ThrowingRunnable.Named(reactivestreams.tour.QuickTour.class, + () -> reactivestreams.tour.QuickTour.main(arguments)) + ).map(ThrowingRunnable::runAndCatch) + .filter(Objects::nonNull) + .toList(); + if (!errors.isEmpty()) { + AssertionError error = new AssertionError(String.format("%d %s failed", + errors.size(), errors.size() == 1 ? "application" : "applications")); + errors.forEach(error::addSuppressed); + throw error; + } + } + + private NativeImageApp() { + } + + private interface ThrowingRunnable { + void run() throws Exception; + + @Nullable + default Throwable runAndCatch() { + try { + run(); + } catch (Exception | AssertionError e) { + return e; + } + return null; + } + + final class Named implements ThrowingRunnable { + private final String name; + private final ThrowingRunnable runnable; + + Named(final String name, final ThrowingRunnable runnable) { + this.name = name; + this.runnable = runnable; + } + + Named(final Class mainClass, final ThrowingRunnable runnable) { + this(mainClass.getName(), runnable); + } + + @Override + public void run() throws Exception { + runnable.run(); + } + + @Override + @Nullable + public Throwable runAndCatch() { + Throwable t = runnable.runAndCatch(); + if (t != null) { + t = new AssertionError(name, t); + } + return t; + } + } + } +} diff --git a/graalvm-native-image-app/src/main/resources/META-INF/native-image/jni-config.json b/graalvm-native-image-app/src/main/resources/META-INF/native-image/jni-config.json new file mode 100644 index 00000000000..c1dcb7f2ded --- /dev/null +++ b/graalvm-native-image-app/src/main/resources/META-INF/native-image/jni-config.json @@ -0,0 +1,184 @@ +[ +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_crypto_fn", + "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"] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hash_fn", + "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"] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hmac_fn", + "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"] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_log_fn_t", + "methods":[{"name":"log","parameterTypes":["int","com.mongodb.crypt.capi.CAPI$cstring","int","com.sun.jna.Pointer"] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_random_fn", + "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"] }] +}, +{ + "name":"com.mongodb.internal.graalvm.NativeImageApp", + "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] +}, +{ + "name":"com.sun.jna.Callback" +}, +{ + "name":"com.sun.jna.CallbackReference", + "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"] }] +}, +{ + "name":"com.sun.jna.CallbackReference$AttachOptions" +}, +{ + "name":"com.sun.jna.FromNativeConverter", + "methods":[{"name":"nativeType","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.IntegerType", + "fields":[{"name":"value"}] +}, +{ + "name":"com.sun.jna.JNIEnv" +}, +{ + "name":"com.sun.jna.Native", + "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"] }] +}, +{ + "name":"com.sun.jna.Native$ffi_callback", + "methods":[{"name":"invoke","parameterTypes":["long","long","long"] }] +}, +{ + "name":"com.sun.jna.NativeMapped", + "methods":[{"name":"toNative","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.Pointer", + "fields":[{"name":"peer"}], + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"com.sun.jna.PointerType", + "fields":[{"name":"pointer"}] +}, +{ + "name":"com.sun.jna.Structure", + "fields":[{"name":"memory"}, {"name":"typeInfo"}], + "methods":[{"name":"autoRead","parameterTypes":[] }, {"name":"autoWrite","parameterTypes":[] }, {"name":"getTypeInfo","parameterTypes":[] }, {"name":"newInstance","parameterTypes":["java.lang.Class","long"] }] +}, +{ + "name":"com.sun.jna.Structure$ByValue" +}, +{ + "name":"com.sun.jna.Structure$FFIType$FFITypes", + "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"}] +}, +{ + "name":"com.sun.jna.WString", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Boolean", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["boolean"] }, {"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Byte", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["byte"] }] +}, +{ + "name":"java.lang.Character", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["char"] }] +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"getComponentType","parameterTypes":[] }] +}, +{ + "name":"java.lang.Double", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["double"] }] +}, +{ + "name":"java.lang.Float", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["float"] }] +}, +{ + "name":"java.lang.Integer", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["int"] }] +}, +{ + "name":"java.lang.Long", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"java.lang.Object", + "methods":[{"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.Short", + "fields":[{"name":"TYPE"}, {"name":"value"}], + "methods":[{"name":"","parameterTypes":["short"] }] +}, +{ + "name":"java.lang.String", + "methods":[{"name":"","parameterTypes":["byte[]"] }, {"name":"","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":[] }] +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"java.lang.UnsatisfiedLinkError", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Void", + "fields":[{"name":"TYPE"}] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"getParameterTypes","parameterTypes":[] }, {"name":"getReturnType","parameterTypes":[] }] +}, +{ + "name":"java.nio.Buffer", + "methods":[{"name":"position","parameterTypes":[] }] +}, +{ + "name":"java.nio.ByteBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.CharBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.DoubleBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.FloatBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.IntBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.LongBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +}, +{ + "name":"java.nio.ShortBuffer", + "methods":[{"name":"array","parameterTypes":[] }, {"name":"arrayOffset","parameterTypes":[] }] +} +] \ No newline at end of file diff --git a/graalvm-native-image-app/src/main/resources/META-INF/native-image/predefined-classes-config.json b/graalvm-native-image-app/src/main/resources/META-INF/native-image/predefined-classes-config.json new file mode 100644 index 00000000000..847895071fb --- /dev/null +++ b/graalvm-native-image-app/src/main/resources/META-INF/native-image/predefined-classes-config.json @@ -0,0 +1,7 @@ +[ + { + "type":"agent-extracted", + "classes":[ + ] + } +] diff --git a/graalvm-native-image-app/src/main/resources/META-INF/native-image/proxy-config.json b/graalvm-native-image-app/src/main/resources/META-INF/native-image/proxy-config.json new file mode 100644 index 00000000000..32960f8ced3 --- /dev/null +++ b/graalvm-native-image-app/src/main/resources/META-INF/native-image/proxy-config.json @@ -0,0 +1,2 @@ +[ +] \ No newline at end of file diff --git a/graalvm-native-image-app/src/main/resources/META-INF/native-image/reflect-config.json b/graalvm-native-image-app/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 00000000000..29ded3e5f40 --- /dev/null +++ b/graalvm-native-image-app/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,446 @@ +[ +{ + "name":"boolean", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +}, +{ + "name":"ch.qos.logback.classic.encoder.PatternLayoutEncoder", + "queryAllPublicMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.joran.SerializedModelConfigurator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.DateConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LevelConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LineSeparatorConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LoggerConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.MessageConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.ThreadConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.util.DefaultJoranConfigurator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.core.ConsoleAppender", + "queryAllPublicMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.core.OutputStreamAppender", + "methods":[{"name":"setEncoder","parameterTypes":["ch.qos.logback.core.encoder.Encoder"] }] +}, +{ + "name":"ch.qos.logback.core.encoder.Encoder", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"ch.qos.logback.core.encoder.LayoutWrappingEncoder", + "methods":[{"name":"setParent","parameterTypes":["ch.qos.logback.core.spi.ContextAware"] }] +}, +{ + "name":"ch.qos.logback.core.pattern.PatternLayoutEncoderBase", + "methods":[{"name":"setPattern","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"ch.qos.logback.core.spi.ContextAware", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.mongodb.BasicDBObject", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.MongoNamespace", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.mongodb.WriteConcern", + "allPublicFields":true +}, +{ + "name":"com.mongodb.client.model.changestream.ChangeStreamDocument", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","org.bson.BsonDocument","org.bson.BsonDocument","org.bson.BsonDocument","java.lang.Object","java.lang.Object","org.bson.BsonDocument","org.bson.BsonTimestamp","com.mongodb.client.model.changestream.UpdateDescription","org.bson.BsonInt64","org.bson.BsonDocument","org.bson.BsonDateTime","com.mongodb.client.model.changestream.SplitEvent","org.bson.BsonDocument"] }] +}, +{ + "name":"com.mongodb.client.model.changestream.SplitEvent", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.mongodb.client.model.changestream.TruncatedArray", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.mongodb.client.model.changestream.UpdateDescription", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.util.List","org.bson.BsonDocument","java.util.List","org.bson.BsonDocument"] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI", + "allPublicFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$cstring", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_binary_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_crypto_fn", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_ctx_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hash_fn", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_hmac_fn", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_kms_ctx_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_log_fn_t", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_random_fn", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_status_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.mongodb.crypt.capi.CAPI$mongocrypt_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.CallbackProxy", + "methods":[{"name":"callback","parameterTypes":["java.lang.Object[]"] }] +}, +{ + "name":"com.sun.jna.Pointer", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +}, +{ + "name":"com.sun.jna.Structure$FFIType", + "allDeclaredFields":true, + "queryAllPublicConstructors":true, + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.Structure$FFIType$size_t", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.jna.ptr.PointerByReference", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"int", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +}, +{ + "name":"java.io.FilePermission" +}, +{ + "name":"java.lang.Object", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.Record" +}, +{ + "name":"java.lang.RuntimePermission" +}, +{ + "name":"java.lang.Thread", + "fields":[{"name":"threadLocalRandomProbe"}] +}, +{ + "name":"java.lang.Throwable", + "methods":[{"name":"addSuppressed","parameterTypes":["java.lang.Throwable"] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"isVarArgs","parameterTypes":[] }] +}, +{ + "name":"java.net.NetPermission" +}, +{ + "name":"java.net.Socket", + "methods":[{"name":"setOption","parameterTypes":["java.net.SocketOption","java.lang.Object"] }] +}, +{ + "name":"java.net.SocketPermission" +}, +{ + "name":"java.net.URLPermission", + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"java.nio.Buffer" +}, +{ + "name":"java.security.AllPermission" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.security.SecurityPermission" +}, +{ + "name":"java.util.PropertyPermission" +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64", + "fields":[{"name":"base"}, {"name":"cellsBusy"}] +}, +{ + "name":"javax.smartcardio.CardPermission" +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, +{ + "name":"jdk.net.ExtendedSocketOptions", + "fields":[{"name":"TCP_KEEPCOUNT"}, {"name":"TCP_KEEPIDLE"}, {"name":"TCP_KEEPINTERVAL"}] +}, +{ + "name":"long", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +}, +{ + "name":"org.bson.codecs.kotlin.DataClassCodecProvider" +}, +{ + "name":"org.bson.codecs.kotlinx.KotlinSerializerCodecProvider" +}, +{ + "name":"org.bson.codecs.record.RecordCodecProvider" +}, +{ + "name":"org.slf4j.Logger" +}, +{ + "name":"reactivestreams.tour.Address", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getCity","parameterTypes":[] }, {"name":"getStreet","parameterTypes":[] }, {"name":"getZip","parameterTypes":[] }, {"name":"setCity","parameterTypes":["java.lang.String"] }, {"name":"setStreet","parameterTypes":["java.lang.String"] }, {"name":"setZip","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"reactivestreams.tour.Person", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getAddress","parameterTypes":[] }, {"name":"getAge","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"setAddress","parameterTypes":["reactivestreams.tour.Address"] }, {"name":"setAge","parameterTypes":["int"] }, {"name":"setId","parameterTypes":["org.bson.types.ObjectId"] }, {"name":"setName","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"reactor.core.publisher.BaseSubscriber", + "fields":[{"name":"subscription"}] +}, +{ + "name":"reactor.core.publisher.FlatMapTracker", + "fields":[{"name":"size"}] +}, +{ + "name":"reactor.core.publisher.FluxConcatArray$ConcatArraySubscriber", + "fields":[{"name":"requested"}] +}, +{ + "name":"reactor.core.publisher.FluxCreate$BaseSink", + "fields":[{"name":"disposable"}, {"name":"requestConsumer"}, {"name":"requested"}] +}, +{ + "name":"reactor.core.publisher.FluxCreate$BufferAsyncSink", + "fields":[{"name":"wip"}] +}, +{ + "name":"reactor.core.publisher.FluxCreate$SerializedFluxSink", + "fields":[{"name":"error"}, {"name":"wip"}] +}, +{ + "name":"reactor.core.publisher.FluxDoFinally$DoFinallySubscriber", + "fields":[{"name":"once"}] +}, +{ + "name":"reactor.core.publisher.FluxFlatMap$FlatMapInner", + "fields":[{"name":"s"}] +}, +{ + "name":"reactor.core.publisher.FluxFlatMap$FlatMapMain", + "fields":[{"name":"error"}, {"name":"requested"}, {"name":"wip"}] +}, +{ + "name":"reactor.core.publisher.FluxIterable$IterableSubscription", + "fields":[{"name":"requested"}] +}, +{ + "name":"reactor.core.publisher.LambdaMonoSubscriber", + "fields":[{"name":"subscription"}] +}, +{ + "name":"reactor.core.publisher.LambdaSubscriber", + "fields":[{"name":"subscription"}] +}, +{ + "name":"reactor.core.publisher.MonoCallable$MonoCallableSubscription", + "fields":[{"name":"requestedOnce"}] +}, +{ + "name":"reactor.core.publisher.MonoCreate$DefaultMonoSink", + "fields":[{"name":"disposable"}, {"name":"requestConsumer"}, {"name":"state"}] +}, +{ + "name":"reactor.core.publisher.MonoFlatMap$FlatMapMain", + "fields":[{"name":"second"}] +}, +{ + "name":"reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain", + "fields":[{"name":"inner"}, {"name":"requested"}] +}, +{ + "name":"reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain", + "fields":[{"name":"state"}] +}, +{ + "name":"reactor.core.publisher.MonoNext$NextSubscriber", + "fields":[{"name":"wip"}] +}, +{ + "name":"reactor.core.publisher.Operators$BaseFluxToMonoOperator", + "fields":[{"name":"state"}] +}, +{ + "name":"reactor.core.publisher.Operators$MultiSubscriptionSubscriber", + "fields":[{"name":"missedProduced"}, {"name":"missedRequested"}, {"name":"missedSubscription"}, {"name":"wip"}] +}, +{ + "name":"reactor.core.publisher.StrictSubscriber", + "fields":[{"name":"error"}, {"name":"requested"}, {"name":"s"}, {"name":"wip"}] +}, +{ + "name":"reactor.util.concurrent.MpscLinkedQueue", + "fields":[{"name":"consumerNode"}, {"name":"producerNode"}] +}, +{ + "name":"reactor.util.concurrent.MpscLinkedQueue$LinkedQueueNode", + "fields":[{"name":"next"}] +}, +{ + "name":"reactor.util.concurrent.SpscLinkedArrayQueue", + "fields":[{"name":"consumerIndex"}, {"name":"producerIndex"}] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"tour.Address", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getCity","parameterTypes":[] }, {"name":"getStreet","parameterTypes":[] }, {"name":"getZip","parameterTypes":[] }, {"name":"setCity","parameterTypes":["java.lang.String"] }, {"name":"setStreet","parameterTypes":["java.lang.String"] }, {"name":"setZip","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"tour.Person", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getAddress","parameterTypes":[] }, {"name":"getAge","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"setAddress","parameterTypes":["tour.Address"] }, {"name":"setAge","parameterTypes":["int"] }, {"name":"setId","parameterTypes":["org.bson.types.ObjectId"] }, {"name":"setName","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"void", + "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}] +} +] \ No newline at end of file diff --git a/graalvm-native-image-app/src/main/resources/META-INF/native-image/resource-config.json b/graalvm-native-image-app/src/main/resources/META-INF/native-image/resource-config.json new file mode 100644 index 00000000000..ece741c68e4 --- /dev/null +++ b/graalvm-native-image-app/src/main/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,57 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E" + }, { + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.nio.channels.spi.AsynchronousChannelProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qcom/sun/jna/darwin-aarch64/libjnidispatch.jnilib\\E" + }, { + "pattern":"\\Qcom/sun/jna/darwin-x86-64/libjnidispatch.jnilib\\E" + }, { + "pattern":"\\Qcom/sun/jna/linux-aarch64/libjnidispatch.so\\E" + }, { + "pattern":"\\Qcom/sun/jna/linux-ppc64le/libjnidispatch.so\\E" + }, { + "pattern":"\\Qcom/sun/jna/linux-s390x/libjnidispatch.so\\E" + }, { + "pattern":"\\Qcom/sun/jna/linux-x86-64/libjnidispatch.so\\E" + }, { + "pattern":"\\Qcom/sun/jna/win32-x86-64/jnidispatch.dll\\E" + }, { + "pattern":"\\Qdarwin/libmongocrypt.dylib\\E" + }, { + "pattern":"\\Qlinux-aarch64/libmongocrypt.so\\E" + }, { + "pattern":"\\Qlinux-ppc64le/libmongocrypt.so\\E" + }, { + "pattern":"\\Qlinux-s390x/libmongocrypt.so\\E" + }, { + "pattern":"\\Qlinux-x86-64/libmongocrypt.so\\E" + }, { + "pattern":"\\Qwin32-x86-64/mongocrypt.dll\\E" + }, { + "pattern":"\\Qlogback-test.scmo\\E" + }, { + "pattern":"\\Qlogback-test.xml\\E" + }, { + "pattern":"\\Qlogback.scmo\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfc.nrm\\E" + }]}, + "bundles":[] +} diff --git a/graalvm-native-image-app/src/main/resources/META-INF/native-image/serialization-config.json b/graalvm-native-image-app/src/main/resources/META-INF/native-image/serialization-config.json new file mode 100644 index 00000000000..d0304f2a1c7 --- /dev/null +++ b/graalvm-native-image-app/src/main/resources/META-INF/native-image/serialization-config.json @@ -0,0 +1,8 @@ +{ + "types":[ + ], + "lambdaCapturingTypes":[ + ], + "proxies":[ + ] +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 66e256e6d55..e31c63fbd62 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,4 @@ scalaVersions=2.11.12,2.12.15,2.13.6 defaultScalaVersions=2.13.6 runOnceTasks=clean,release org.gradle.java.installations.auto-download=false -org.gradle.java.installations.fromEnv=JDK8,JDK11,JDK17,JDK21 +org.gradle.java.installations.fromEnv=JDK8,JDK11,JDK17,JDK21,JDK21_GRAALVM diff --git a/gradle/javaToolchain.gradle b/gradle/javaToolchain.gradle index 5a1d34e1c6c..ea7f64cfd5d 100644 --- a/gradle/javaToolchain.gradle +++ b/gradle/javaToolchain.gradle @@ -30,12 +30,61 @@ allprojects { } } } + project.pluginManager.withPlugin('org.graalvm.buildtools.native') { + def minRequiredGraalVMJavaVersion = 21 + // By configuring the toolchains for the `org.graalvm.buildtools.native` plugin + // conditionally, we avoid Gradle errors caused by it failing to locate an installed GraalVM + // for Java SE older than 21. One situation when this is relevant is building from an IDE, + // where the `DEFAULT_JDK_VERSION` is likely used. + if (javaVersion >= minRequiredGraalVMJavaVersion) { + def javaLanguageVersion = JavaLanguageVersion.of(javaVersion) + // `JvmVendorSpec.GRAAL_VM` matches only GraalVM Community (https://github.com/graalvm/graalvm-ce-builds/releases), + // and does not match any other Graal distribution. + // That is, Gradle fails to locate any other installed distribution of GraalVM. + // Furthermore, there is no other way to express via the Gradle toolchain functionality + // that GraalVM must be used. The documentation of the `org.graalvm.buildtools.native` plugin + // says the following about this limitation: + // "be aware that the toolchain detection cannot distinguish between GraalVM JDKs + // and standard JDKs without Native Image support: + // if you have both installed on the machine, Gradle may randomly pick one or the other". + // Fortunately, `JvmVendorSpec.GRAAL_VM` makes things less hideous than that. + // + // The documentation of the `org.graalvm.buildtools.native` plugin mentions + // the environment variable `GRAALVM_HOME` as an alternative to Gradle toolchain functionality. + // I was unable to find a way to stop relying on the toolchain specification requiring `JvmVendorSpec.GRAAL_VM` + // even with `GRAALVM_HOME`. + def graalVendor = JvmVendorSpec.GRAAL_VM + graalvmNative { + agent { + java { + toolchain { + languageVersion = javaLanguageVersion + vendor = graalVendor + } + } + } + binaries { + configureEach { + javaLauncher = javaToolchains.launcherFor { + languageVersion = javaLanguageVersion + vendor = graalVendor + } + } + } + } + } + } if (project == project(":bson-record-codec")) { tasks.withType(JavaCompile) { options.encoding = "UTF-8" options.release.set(17) } + } else if (project == project(':graalvm-native-image-app')) { + tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + options.release.set(DEFAULT_JDK_VERSION) + } } else if (project in javaMainProjects) { tasks.withType(JavaCompile) { options.encoding = "UTF-8" diff --git a/settings.gradle b/settings.gradle index 22ac7c67bfd..ab252727079 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,3 +31,4 @@ include ':bson-scala' include ':driver-scala' include 'util:spock' include 'util:taglets' +include ':graalvm-native-image-app' From 3c92447fa4f300435d051e9405247c986d4ba139 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Wed, 20 Mar 2024 13:14:38 -0600 Subject: [PATCH 2/8] Correctly propagate relevant Java system properties when running `graalvm-native-image-app` JAVA-3580 --- graalvm-native-image-app/build.gradle | 10 ++++++ graalvm-native-image-app/readme.md | 33 ++++++++++++++----- .../internal/graalvm/NativeImageApp.java | 2 ++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/graalvm-native-image-app/build.gradle b/graalvm-native-image-app/build.gradle index 1a62dc40d7f..c024e71a6f3 100644 --- a/graalvm-native-image-app/build.gradle +++ b/graalvm-native-image-app/build.gradle @@ -23,6 +23,11 @@ application { mainClass = 'com.mongodb.internal.graalvm.NativeImageApp' } +def systemPropertiesForRunningNativeApp = System.getProperties().findAll { it.key.toString().startsWith("org.mongodb.") } +tasks.matching { it.name == 'run' }.configureEach { + systemProperties(systemPropertiesForRunningNativeApp) +} + // see https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html graalvmNative { metadataRepository { @@ -46,6 +51,7 @@ graalvmNative { binaries { configureEach { buildArgs.add('--strict-image-heap') + buildArgs.add('-H:+UnlockExperimentalVMOptions') // see class initialization report is generated at `graalvm/build/native/nativeCompile/reports`, // informing us on the kind of initialization for each Java class buildArgs.add('-H:+PrintClassInitialization') @@ -57,6 +63,10 @@ graalvmNative { sharedLibrary = false def mainClassName = application.mainClass.get() imageName = mainClassName.substring(mainClassName.lastIndexOf('.') + 1, mainClassName.length()) + runtimeArgs.addAll(systemPropertiesForRunningNativeApp.entrySet() + .stream() + .map {"-D${it.getKey()}=${it.getValue()}" } + .toList()) quickBuild = true // See the "Apply" entries in the `native-image` built-time output, informing us on // the build configuration files (https://www.graalvm.org/latest/reference-manual/native-image/overview/BuildConfiguration/) diff --git a/graalvm-native-image-app/readme.md b/graalvm-native-image-app/readme.md index 7af8970e6af..4e15dcef6cb 100644 --- a/graalvm-native-image-app/readme.md +++ b/graalvm-native-image-app/readme.md @@ -38,11 +38,28 @@ export JDK21_GRAALVM="/Users/valentin.kovalenko/.sdkman/candidates/java/21.0.2-g ### Build-related commands -Run from the driver project root directory: - -| # | Command | Description | -|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| -| 0 | `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`. | -| 1 | `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 before building the application only if building fails otherwise. | -| 2 | `./graalvm-native-image-app/build/native/nativeCompile/NativeImageApp` | Run the application that has been built. | -| 3 | `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. | +Assuming that your MongoDB deployment is accessible at `mongodb://localhost:27017`, +run from the driver project root directory: + +| # | Command | Description | +|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| 0 | `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`. | +| 1 | `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 before building the application only if building fails otherwise. | +| 2 | `./graalvm-native-image-app/build/native/nativeCompile/NativeImageApp` | Run the application that has been built. | +| 3 | `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. | + +#### Specifying a custom connection string + +If your MongoDB deployment is not accessible at `mongodb://localhost:27017`, +or you want to use a custom connection string, +you can specify the connection string used by the `:graalvm-native-image-app:run`, `:graalvm-native-image-app:nativeRun` +Gradle tasks, as well as by the built native application by passing the CLI argument +`-Dorg.mongodb.test.uri=""` to `gradlew` or `NativeImageApp` respectively: + +```bash +./gradlew ... -Dorg.mongodb.test.uri="" +``` + +```bash +./graalvm-native-image-app/build/native/nativeCompile/NativeImageApp -Dorg.mongodb.test.uri="" +``` diff --git a/graalvm-native-image-app/src/main/com/mongodb/internal/graalvm/NativeImageApp.java b/graalvm-native-image-app/src/main/com/mongodb/internal/graalvm/NativeImageApp.java index 6afdb339315..ff41a4829b0 100644 --- a/graalvm-native-image-app/src/main/com/mongodb/internal/graalvm/NativeImageApp.java +++ b/graalvm-native-image-app/src/main/com/mongodb/internal/graalvm/NativeImageApp.java @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nullable; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Stream; @@ -31,6 +32,7 @@ public static void main(final String[] args) { LOGGER.info("java.vendor={}, java.vm.name={}, java.version={}", System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.version")); String[] arguments = new String[] {getConnectionStringSystemPropertyOrDefault()}; + LOGGER.info("proper args={}, tour/example arguments={}", Arrays.toString(args), Arrays.toString(arguments)); List errors = Stream.of( new ThrowingRunnable.Named(gridfs.GridFSTour.class, () -> gridfs.GridFSTour.main(arguments)), From 280b2d91c9f37ad2852e262e0c2b136a141a5d06 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Wed, 20 Mar 2024 13:24:18 -0600 Subject: [PATCH 3/8] Remove MongoDB 7.0 from the `graalvm-native-image-app` Evergreen matrix JAVA-3580 --- .evergreen/.evg.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/.evg.yml b/.evergreen/.evg.yml index a321334c36a..57465d323fb 100644 --- a/.evergreen/.evg.yml +++ b/.evergreen/.evg.yml @@ -2301,7 +2301,7 @@ buildvariants: batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README - matrix_name: "graalvm-native-image-app" - matrix_spec: { version: [ "5.0", "7.0" ], topology: [ "replicaset" ], auth: [ "noauth" ], ssl: [ "nossl" ], + matrix_spec: { version: [ "7.0" ], topology: [ "replicaset" ], auth: [ "noauth" ], ssl: [ "nossl" ], jdk: [ "jdk21" ], os: [ "linux" ] } display_name: "GraalVM native image app: ${version} ${topology} ${auth} ${ssl} ${jdk} ${os}" tasks: From d59810996bfea38a35bc060ff33ff35f5d89d5f2 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Wed, 20 Mar 2024 13:50:18 -0600 Subject: [PATCH 4/8] Disable `metadataRepository` JAVA-3580 --- graalvm-native-image-app/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graalvm-native-image-app/build.gradle b/graalvm-native-image-app/build.gradle index c024e71a6f3..b7efd926584 100644 --- a/graalvm-native-image-app/build.gradle +++ b/graalvm-native-image-app/build.gradle @@ -31,8 +31,7 @@ tasks.matching { it.name == 'run' }.configureEach { // see https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html graalvmNative { metadataRepository { - enabled = true - version = '0.3.6' + enabled = false } agent { // Executing the `run` Gradle task with the tracing agent From bbccbd9dc1857d6cd52cccc81256bbab4cac14cd Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Wed, 20 Mar 2024 14:28:15 -0600 Subject: [PATCH 5/8] Set `MONGODB_URI` in the `run graalvm native image app` Evergreen function when running `.evergreen/run-graalvm-native-image-app.sh` JAVA-3580 --- .evergreen/.evg.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/.evg.yml b/.evergreen/.evg.yml index 57465d323fb..3f6f147b423 100644 --- a/.evergreen/.evg.yml +++ b/.evergreen/.evg.yml @@ -889,7 +889,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - JAVA_VERSION="${JAVA_VERSION}" .evergreen/run-graalvm-native-image-app.sh + MONGODB_URI="${MONGODB_URI}" JAVA_VERSION="${JAVA_VERSION}" .evergreen/run-graalvm-native-image-app.sh # Anchors From 2ec2a6705a961d1eb66179dd43b4190f35bb72f9 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Wed, 20 Mar 2024 15:33:19 -0600 Subject: [PATCH 6/8] Improve `graalvm-native-image-app/readme.md` JAVA-3580 --- graalvm-native-image-app/readme.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/graalvm-native-image-app/readme.md b/graalvm-native-image-app/readme.md index 4e15dcef6cb..3eea1496e25 100644 --- a/graalvm-native-image-app/readme.md +++ b/graalvm-native-image-app/readme.md @@ -36,6 +36,12 @@ export JDK17="/Users/valentin.kovalenko/.sdkman/candidates/java/17.0.10-librca/" export JDK21_GRAALVM="/Users/valentin.kovalenko/.sdkman/candidates/java/21.0.2-graalce/" ``` +##### Informing Gradle on JDK locations it does not know about + +If `JDK21_GRAALVM` points to a +[location the Gradle auto-detection mechanism is not aware of](https://docs.gradle.org/current/userguide/toolchains.html#sec:auto_detection), +you need to inform Gradle about that location as specified in https://docs.gradle.org/current/userguide/toolchains.html#sec:custom_loc. + ### Build-related commands Assuming that your MongoDB deployment is accessible at `mongodb://localhost:27017`, From 9633e9caa32e73d34ecea5c22f12e1340feab2e8 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Thu, 21 Mar 2024 11:20:24 -0600 Subject: [PATCH 7/8] Minor improvements JAVA-3580 --- .evergreen/run-graalvm-native-image-app.sh | 2 +- gradle/javaToolchain.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.evergreen/run-graalvm-native-image-app.sh b/.evergreen/run-graalvm-native-image-app.sh index e864b2db491..130b0ef7b4e 100755 --- a/.evergreen/run-graalvm-native-image-app.sh +++ b/.evergreen/run-graalvm-native-image-app.sh @@ -6,7 +6,7 @@ set -o errexit -RELATIVE_DIR_PATH="$(dirname "${BASH_SOURCE[0]:-$0}")" +readonly RELATIVE_DIR_PATH="$(dirname "${BASH_SOURCE[0]:-$0}")" source "${RELATIVE_DIR_PATH}/javaConfig.bash" echo "MONGODB_URI: ${MONGODB_URI}" diff --git a/gradle/javaToolchain.gradle b/gradle/javaToolchain.gradle index ea7f64cfd5d..187b143eea6 100644 --- a/gradle/javaToolchain.gradle +++ b/gradle/javaToolchain.gradle @@ -39,7 +39,7 @@ allprojects { if (javaVersion >= minRequiredGraalVMJavaVersion) { def javaLanguageVersion = JavaLanguageVersion.of(javaVersion) // `JvmVendorSpec.GRAAL_VM` matches only GraalVM Community (https://github.com/graalvm/graalvm-ce-builds/releases), - // and does not match any other Graal distribution. + // and does not match any other GraalVM distribution. // That is, Gradle fails to locate any other installed distribution of GraalVM. // Furthermore, there is no other way to express via the Gradle toolchain functionality // that GraalVM must be used. The documentation of the `org.graalvm.buildtools.native` plugin From e7a97d9feb44d2be9eae30c21e334d844f840e83 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Mon, 25 Mar 2024 10:18:52 -0600 Subject: [PATCH 8/8] Use `~` in `readme.md` JAVA-3580 --- graalvm-native-image-app/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graalvm-native-image-app/readme.md b/graalvm-native-image-app/readme.md index 3eea1496e25..bb974fdd063 100644 --- a/graalvm-native-image-app/readme.md +++ b/graalvm-native-image-app/readme.md @@ -32,8 +32,8 @@ Assuming that the JDK you are using for running Gradle is for Java SE 17, export (your values may differ): ```bash -export JDK17="/Users/valentin.kovalenko/.sdkman/candidates/java/17.0.10-librca/" -export JDK21_GRAALVM="/Users/valentin.kovalenko/.sdkman/candidates/java/21.0.2-graalce/" +export JDK17=$(realpath ~/".sdkman/candidates/java/17.0.10-librca/") +export JDK21_GRAALVM=$(realpath ~/".sdkman/candidates/java/21.0.2-graalce/") ``` ##### Informing Gradle on JDK locations it does not know about