I have searched for some time for a Gradle plug-in which will:
I noticed that with Micronaut it is quite easy to do by adding the following to the build.gradle file:
plugins {
id("io.micronaut.application") version "4.4.4"
id("io.micronaut.aot") version "4.4.4"
...
}
...
graalvmNative.toolchainDetection = false
micronaut {
runtime("lambda_provided")
testRuntime("junit5")
processing {
incremental(true)
}
aot {
// Please review carefully the optimizations enabled below
// Check https://micronaut-projects.github.io/micronaut-aot/latest/guide/ for more details
optimizeServiceLoading = false
convertYamlToJava = false
precomputeOperations = true
cacheEnvironment = true
optimizeClassLoading = true
deduceEnvironment = true
optimizeNetty = true
replaceLogbackXml = true
}
}
tasks.named("dockerfileNative") {
jdkVersion = "21"
args(
"-XX:MaximumHeapSizePercent=80",
"-Dio.netty.allocator.numDirectArenas=0",
"-Dio.netty.noPreferDirect=true"
)
}
Then building a GraalVM Zip is as simple as:
./gradlew clean buildNativeLambda
This will create a file (which can be uploaded to an AWS Lambda) e.g.
build/libs/lambda-test-java-0.1-all.jar
However, I'm try to do this on a plain Java project i.e. with no frameworks as I want to get the code to build and to execute as fast as possible.
There seem to be some Maven plugin solutions and the SAM CLI tool only seems to support Java 17 - but no Gradle Plugins unless looking in wrong place.
Posting an answer here (tested on Mac) which includes a working "Hello World Lambda" example at:
In brief, the solution was to cherry-pick files from 2 locations:
Firstly I copied the Dockerfile
from the AWS example and removed the maven
section (this would never be used):
FROM public.ecr.aws/amazonlinux/amazonlinux:2023
RUN yum -y update \
&& yum install -y unzip tar gzip bzip2-devel ed gcc gcc-c++ gcc-gfortran \
less libcurl-devel openssl openssl-devel readline-devel xz-devel \
zlib-devel glibc-static zlib-static \
&& rm -rf /var/cache/yum
# Graal VM
ENV GRAAL_VERSION 21.0.2
ENV ARCHITECTURE aarch64
ENV GRAAL_FILENAME graalvm-community-jdk-${GRAAL_VERSION}_linux-${ARCHITECTURE}_bin.tar.gz
RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${GRAAL_VERSION}/${GRAAL_FILENAME} | tar -xvz
RUN mv graalvm-community-openjdk-${GRAAL_VERSION}* /usr/lib/graalvm
ENV JAVA_HOME /usr/lib/graalvm
# Gradle
ENV GRADLE_VERSION 7.4.1
ENV GRADLE_FOLDERNAME gradle-${GRADLE_VERSION}
ENV GRADLE_FILENAME gradle-${GRADLE_VERSION}-bin.zip
RUN curl -LO https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip
RUN unzip gradle-${GRADLE_VERSION}-bin.zip
RUN mv $GRADLE_FOLDERNAME /usr/lib/gradle
RUN ln -s /usr/lib/gradle/bin/gradle /usr/bin/gradle
VOLUME /project
WORKDIR /project
WORKDIR /lambda
We can now build a docker image al2023-graalvm21:native-web
(which extends the Amazon amazonlinux:2023
container and installs GraalVM / Gradle tooling):
docker build -t al2023-graalvm21:native-web .
Secondly I ran the SAM CLI tool i.e. sam init
and selected following options:
1 - AWS Quick Start Templates
1 - Hello World Example
6 - graalvm.java17 (provided.al2)
1 - gradle
n
for remaining options.The SAM CLI then generated an example and from that I copied the following elements from the build.gradle
:
plugins {
...
id("application")
id("org.graalvm.buildtools.native") version "0.10.6"
}
...
application {
mainClass.set('com.amazonaws.services.lambda.runtime.api.client.AWSLambda')
}
graalvmNative {
binaries {
main {
imageName = "native"
verbose = true
fallback = false
}
}
}
We can then use our Docker image to build our code base and this will generate a native image in the correct OS (Amazon Linux) by running:
docker run -it -v `pwd`:`pwd` -w `pwd` -v ~/.gradle:/root/.gradle al2023-graalvm21:native-web \
./gradlew clean build -x test nativeCompile
This creates the following build native executable file:
build/native/nativeCompile/native
However, for a Lambda to work it needs a Zip file with both the native
executable and a boostrap
entry-point file e.g.
#!/bin/sh
set -e
./native "lambda.test.HelloLambdaHandler"
Interestingly the SAM CLI uses a Makefile
to create the final Zip e.g.
build-HelloWorldFunction:
rm -rf ./build
./gradlew clean
./gradlew nativeCompile
cp ./build/native/nativeCompile/native $(ARTIFACTS_DIR)
cp ./build/resources/main/bootstrap $(ARTIFACTS_DIR)
However, I didn't want to use SAM CLI or Make but instead added a new Gradle task buildNativeLambda
to zip these 2 files:
// Build ZIP file which includes the `bootstrap` script (required for AWS Lambdas) and `native` GraalVM executable
task buildNativeLambda(type: Zip) {
archiveBaseName = rootProject.name + "-native"
from(files('build/native/nativeCompile/native'))
from(files('build/resources/main/bootstrap'))
}
buildNativeLambda.dependsOn nativeCompile
I then created a handy build-native.sh
script file to do everything i.e. build
docker image, run docker image with buildNativeLambda
task:
#!/usr/bin/env bash
docker build -t al2023-graalvm21:native-web .
docker run -it -v `pwd`:`pwd` -w `pwd` -v ~/.gradle:/root/.gradle al2023-graalvm21:native-web \
./gradlew clean build -x test buildNativeLambda
Running this script (./build-native.sh
) creates the following build Zip file:
build/distributions/java-aws-lambda-graalvm-native.zip
Which can then be uploaded to AWS Lambda:
However, in AWS Lambda test screen I got some reflection errors so I copied all the reflect-config.json
files from the SAM CLI generated app and it finally worked!
More detail exists in the example GitHub README.md.