Search code examples
gradlegradle-pluginmicronautgraalvmgraalvm-native-image

Choose custom GraalVM version to build Micronaut native image


I am trying to build a Micronaut application as a native image for AWS Lambda. But somehow, the build is not picking up the GraalVM version which is installed locally, but rather it downloads the GraalVM version on its own. I have searched few of the documentations, but didn't find anywhere how we can disable/change this behavior.

Here is how I build the native image: ./gradlew clean buildNativeLambda -Pmicronaut.runtime=lambda

Have already set the JAVA_HOME and GRAALVM_HOME to my custom GraalVM version.

Even tried passing following params, but nothing seems to be working: ./gradlew clean -Porg.gradle.java.installations.auto-download=false -Porg.gradle.java.installations.auto-detect=false -Porg.gradle.java.installations.fromEnv=JAVA_HOME buildNativeLambda -Pmicronaut.runtime=lambda

Also tried adding below params to the gradle.properties, but nothing worked:

# Disable Gradle automatic download of Java SDKs
org.gradle.java.installations.auto-download=false
# Disable auto-detection of Java installations
org.gradle.java.installations.auto-detect=false
# Setup explicitly that the Java version to use
# should be the one from the JAVA_HOME environment variable
org.gradle.java.installations.fromEnv=JAVA_HOME

It always generates dockerfile with below steps, where it automatically downloads some GraalVM version on its own. Below are the truncated build logs:

.
.
.
> Task :generateResourcesConfigFile
[native-image-plugin] Resources configuration written into D:\Project\build\native\generated\generateResourcesConfigFile\resource-config.json

> Task :dockerfileNative
Dockerfile written to: D:\Project\build\docker\native-main\DockerfileNative

> Task :dockerBuildNative
Building image using context 'D:\Project\build\docker\native-main'.
Using Dockerfile 'D:\Project\build\docker\native-main\DockerfileNative'
Using images 'project'.
Step 1/27 : FROM amazonlinux:latest AS graalvm
---> 01bdb519be20
Step 2/27 : ENV LANG=en_US.UTF-8
---> Using cache
---> 6bd9aa9f16ff
Step 3/27 : RUN yum install -y gcc gcc-c++ libc6-dev zlib1g-dev curl bash zlib zlib-devel zlib-static zip tar gzip
---> Using cache
---> b0a55dbb7834
Step 4/27 : RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.1.0/graalvm-ce-java11-linux-amd64-22.1.0.tar.gz -o /tmp/graalvm-ce-java11-linux-amd64-22.1.0.tar.gz
---> Using cache
---> 45a9b3d523a3
.
.
.

See Step 4/27 in above build logs. How can we disable that, and instead make it use the GraalVM version which is installed locally on the host machine where build is being generated?

build.gradle:

.
.
.
java {
    sourceCompatibility = JavaVersion.toVersion("11")
    targetCompatibility = JavaVersion.toVersion("11")
}

graalvmNative.toolchainDetection = false
micronaut {
    runtime("lambda_provided")
    testRuntime("junit5")
    processing {
        incremental(true)
        annotations("com.package.*")
    }
}

tasks.named("dockerfileNative") {
    args(
        "-XX:MaximumHeapSizePercent=80",
        "-Dio.netty.allocator.numDirectArenas=0",
        "-Dio.netty.noPreferDirect=true"
    )
}

graalvmNative.binaries.all {
    buildArgs.add("--no-server --pgo-instrument -J-Xmx12g -J-Xms4g -O0")
}

Have followed below links, but didn't get any success yet:

Edit 2022-12-12:

Automatically generated DockerfileNative:

FROM amazonlinux:latest AS graalvm
ENV LANG=en_US.UTF-8
RUN yum install -y gcc gcc-c++ libc6-dev zlib1g-dev curl bash zlib zlib-devel zlib-static zip tar gzip
RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.1.0/graalvm-ce-java11-linux-amd64-22.1.0.tar.gz -o /tmp/graalvm-ce-java11-linux-amd64-22.1.0.tar.gz
RUN tar -zxf /tmp/graalvm-ce-java11-linux-amd64-22.1.0.tar.gz -C /tmp && mv /tmp/graalvm-ce-java11-22.1.0 /usr/lib/graalvm
RUN rm -rf /tmp/*
RUN /usr/lib/graalvm/bin/gu install native-image
CMD ["/usr/lib/graalvm/bin/native-image"]
ENV PATH=/usr/lib/graalvm/bin:${PATH}
FROM graalvm AS builder
WORKDIR /home/app
COPY layers/libs /home/app/libs
COPY layers/classes /home/app/classes
COPY layers/resources /home/app/resources
COPY layers/application.jar /home/app/application.jar
RUN mkdir /home/app/config-dirs
COPY config-dirs/generateResourcesConfigFile /home/app/config-dirs/generateResourcesConfigFile
RUN native-image -cp /home/app/libs/*.jar:/home/app/resources:/home/app/application.jar --no-fallback -H:Name=application -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk=ALL-UNNAMED -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.configure=ALL-UNNAMED -H:ConfigurationFileDirectories=/home/app/config-dirs/generateResourcesConfigFile -H:Class=com.project.Application --no-server -J-Xmx12g -J-Xms4g -O0
FROM amazonlinux:latest
WORKDIR /function
RUN yum install -y zip
COPY --from=builder /home/app/application /function/func
RUN echo "#!/bin/sh" >> bootstrap && echo "set -euo pipefail" >> bootstrap && echo "./func -XX:MaximumHeapSizePercent=80 -Dio.netty.allocator.numDirectArenas=0 -Dio.netty.noPreferDirect=true -Djava.library.path=$(pwd)" >> bootstrap
RUN chmod 777 bootstrap
RUN chmod 777 func
RUN zip -j function.zip bootstrap func
ENTRYPOINT ["/function/func"]

Solution

  • When running buildNativeLambda task of the Micronaut Gradle plugin you delegate the build to the Docker daemon to build the image. Your local GraalVM installation is never used when build Docker images.

    As you can see in your DockerfileNative the build is based on the amazonlinux:latest Docker image and then downloads (line 4) GraalVM 22.1.0 Community Edition. This is done by the Docker daemon running on your machine (or a build machine), completely ignoring your local setup (GraalVM installation).

    RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.1.0/graalvm-ce-java11-linux-amd64-22.1.0.tar.gz -o /tmp/graalvm-ce-java11-linux-amd64-22.1.0.tar.gz
    

    When you take a look at the Gradle plugin source code you see, that you have the possibility to configure the GraalVM version. This will change the generated download link in line 4.

    IMO you can overwrite these values and configure the GraalVM version yourself in your build.gradle file.

    tasks.named("dockerfileNative") {
        graalVersion = '22.3.0' // use 22.3.0 instead of 22.1.0
    }